diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0c73090..a729486 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools">
+
{
override fun getId() = roomSummary.roomId
- override fun getDialogPhoto() = roomSummary.avatarUrl
+ override fun getDialogPhoto() = roomSummary.avatarUrl.ifEmpty { roomSummary.toSDNItem().firstLetterOfDisplayName() }
override fun getDialogName() = roomSummary.displayName
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventMessageWrapper.kt b/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventMessageWrapper.kt
index efe161e..58a85ea 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventMessageWrapper.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventMessageWrapper.kt
@@ -45,4 +45,8 @@ data class TimelineEventMessageWrapper(private val timelineEvent: TimelineEvent)
override fun getUser() = TimelineEventSenderWrapper(timelineEvent.senderInfo)
override fun getCreatedAt() = Date(timelineEvent.root.originServerTs ?: 0)
+
+ fun getTimelineEvent() : TimelineEvent {
+ return timelineEvent
+ }
}
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventSenderWrapper.kt b/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventSenderWrapper.kt
index 5ded1e6..9d34c8b 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventSenderWrapper.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/data/TimelineEventSenderWrapper.kt
@@ -24,5 +24,5 @@ data class TimelineEventSenderWrapper(private val senderInfo: SenderInfo) : IUse
override fun getName() = senderInfo.disambiguatedDisplayName
- override fun getAvatar() = senderInfo.avatarUrl
+ override fun getAvatar() = if (!senderInfo.avatarUrl.isNullOrEmpty()) senderInfo.avatarUrl else "https://static.sending.me/beam/70/${senderInfo.userId}?colors=FC774B,FFB197,B27AFF,DAC2FB,F0E7FD&square=true"
}
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomDetailFragment.kt b/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomDetailFragment.kt
index 6d5d0a1..26159f7 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomDetailFragment.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomDetailFragment.kt
@@ -17,6 +17,7 @@
package org.sdn.android.sdk.sample.ui
import android.content.Context
+import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -41,12 +42,19 @@ import org.sdn.android.sdk.sample.databinding.FragmentRoomDetailBinding
import org.sdn.android.sdk.sample.utils.*
import org.sdn.android.sdk.api.meet.SdnMeetActivity
-import android.util.Log
-import kotlinx.coroutines.GlobalScope
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-//import com.github.zhanghai.android.kotlin.BaseEncoding
import org.apache.commons.codec.binary.Base32
+import org.sdn.android.sdk.api.session.events.model.toModel
+import org.sdn.android.sdk.api.session.room.model.message.MessageContent
+import org.sdn.android.sdk.sample.R
+import org.sdn.android.sdk.sample.data.TimelineEventMessageWrapper
+import org.sdn.android.sdk.sample.ui.dialog.PasswordDialogFragment
class RoomDetailFragment : Fragment(), Timeline.Listener, ToolbarConfigurable {
@@ -112,6 +120,18 @@ class RoomDetailFragment : Fragment(), Timeline.Listener, ToolbarConfigurable {
}
})
+ adapter.setOnMessageLongClickListener {
+ val event = (it as TimelineEventMessageWrapper).getTimelineEvent()
+ with(AlertDialog.Builder(requireContext())) {
+// setTitle("Androidly Alert")
+ setMessage("Request decryption key?")
+ setPositiveButton(R.string.ok) { _: DialogInterface, _: Int ->
+ session.cryptoService().reRequestRoomKeyForEvent(event.root)
+ }
+ setNegativeButton(R.string.cancel, null)
+ show()
+ }
+ }
views.timelineEventList.setAdapter(adapter)
views.timelineEventList.itemAnimator = null
views.timelineEventList.addOnScrollListener(RecyclerScrollMoreListener(views.timelineEventList.layoutManager as LinearLayoutManager) {
@@ -149,11 +169,39 @@ class RoomDetailFragment : Fragment(), Timeline.Listener, ToolbarConfigurable {
}
}
- views.toolbarBtnVideo.setOnClickListener {
- Log.d("getMeeting","start")
- GlobalScope.launch {
- joinRoomMeeting(context!!, roomID)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.room_detail_options, menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.invite -> {
+ val dlg = PasswordDialogFragment.newInstance("")
+ dlg.show(childFragmentManager, "")
+ true
}
+ R.id.meeting -> {
+ room?.roomId?.let {
+ lifecycleScope.launch {
+ joinRoomMeeting(requireContext(), it)
+ }
+ }
+ true
+ }
+ R.id.leave -> {
+ room?.roomId?.let {
+ lifecycleScope.launch {
+ session.roomService().leaveRoom(it)
+ showRoomList()
+ }
+ }
+ true
+ }
+
+ else -> super.onOptionsItemSelected(item)
}
}
@@ -215,5 +263,11 @@ class RoomDetailFragment : Fragment(), Timeline.Listener, ToolbarConfigurable {
}
}
-
+ private fun showRoomList() {
+ (activity as MainActivity).supportFragmentManager
+ .beginTransaction()
+ .addToBackStack(null)
+ .replace(R.id.fragmentContainer, RoomListFragment())
+ .commit()
+ }
}
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomListFragment.kt b/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomListFragment.kt
index 0aa8edf..aadd339 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomListFragment.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/ui/RoomListFragment.kt
@@ -16,9 +16,9 @@
package org.sdn.android.sdk.sample.ui
-import android.content.DialogInterface
-import android.content.res.Resources
+import android.annotation.SuppressLint
import android.os.Bundle
+import android.os.Environment
import android.view.*
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@@ -26,7 +26,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.stfalcon.chatkit.commons.ImageLoader
import com.stfalcon.chatkit.dialogs.DialogsListAdapter
-import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.sdn.android.sdk.api.session.room.model.Membership
import org.sdn.android.sdk.api.session.room.model.RoomSummary
@@ -38,18 +37,47 @@ import org.sdn.android.sdk.sample.SessionHolder
import org.sdn.android.sdk.sample.data.RoomSummaryDialogWrapper
import org.sdn.android.sdk.sample.databinding.FragmentRoomListBinding
import org.sdn.android.sdk.sample.formatter.RoomListDateFormatter
+import org.sdn.android.sdk.sample.ui.dialog.PasswordDialogFragment
import org.sdn.android.sdk.sample.utils.AvatarRenderer
import org.sdn.android.sdk.sample.utils.SDNItemColorProvider
+import timber.log.Timber
+import java.io.File
+import java.io.FileOutputStream
class RoomListFragment : Fragment(), ToolbarConfigurable {
private val session = SessionHolder.currentSession!!
+ private val exportListenerKey = "export"
+ private val importListenerKey = "import"
+ private val e2eBackupDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ private val e2eBackupFile = "e2e_xx_keys.txt"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ childFragmentManager.setFragmentResultListener(exportListenerKey, this) { _, bundle ->
+ val password = bundle.getString("password") ?: "default"
+ lifecycleScope.launch {
+ val exportedBytes = session.cryptoService().exportRoomKeys(password)
+ saveFileToExternalStorage(e2eBackupFile, exportedBytes)
+ }
+ }
+
+ childFragmentManager.setFragmentResultListener(importListenerKey, this) { _, bundle ->
+ val password = bundle.getString("password") ?: "default"
+ lifecycleScope.launch {
+ val importedBytes = readFileFromExternalStorage(e2eBackupFile)
+ val result = session.cryptoService().importRoomKeys(importedBytes, password, null)
+ Timber.i("totalNumberOfKeys: ${result.totalNumberOfKeys}, successfullyNumberOfImportedKeys: ${result.successfullyNumberOfImportedKeys}")
+ }
+ }
+ }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
- ): View? {
+ ): View {
_views = FragmentRoomListBinding.inflate(inflater, container, false)
return views.root
}
@@ -63,19 +91,24 @@ class RoomListFragment : Fragment(), ToolbarConfigurable {
}
private val imageLoader = ImageLoader { imageView, url, _ ->
- avatarRenderer.render(url, imageView)
+ avatarRenderer.renderDrawable(url, imageView)
}
private val roomAdapter = DialogsListAdapter(imageLoader)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
configureToolbar(views.toolbar, displayBack = false)
-11
+
views.createRoomButton.setOnClickListener {
val userId = views.otherUserIdField.text.toString().trim()
viewLifecycleOwner.lifecycleScope.launch {
- session.roomService().createDirectRoom(otherUserId = userId)
+// session.roomService().createDirectRoom(otherUserId = userId)
+ session.roomService().createRoom(CreateRoomParams()
+ .apply {
+ invitedUserIds.add(userId)
+ enableEncryption()
+ })
}
// GlobalScope.launch {
// println("contact-signOut out")
@@ -99,13 +132,13 @@ class RoomListFragment : Fragment(), ToolbarConfigurable {
builder.setMessage("Do you want to join this room?")
builder.setPositiveButton("Join") { _, _ ->
viewLifecycleOwner.lifecycleScope.launch {
- session.roomService().joinRoom(it.roomSummary.roomId);
+ session.roomService().joinRoom(it.roomSummary.roomId)
showRoomDetail(it.roomSummary)
}
}
builder.setNegativeButton("Cancel", null)
val dialog = builder.create()
- dialog.setOnShowListener { _ ->
+ dialog.setOnShowListener {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(resources.getColor(R.color.dark_gray))
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(resources.getColor(R.color.dark_gray))
}
@@ -141,6 +174,16 @@ class RoomListFragment : Fragment(), ToolbarConfigurable {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
+ R.id.export_key -> {
+ val dlg = PasswordDialogFragment.newInstance(exportListenerKey)
+ dlg.show(childFragmentManager, "export")
+ true
+ }
+ R.id.import_key -> {
+ val dlg = PasswordDialogFragment.newInstance(importListenerKey)
+ dlg.show(childFragmentManager, "import")
+ true
+ }
R.id.logout -> {
signOut()
true
@@ -152,7 +195,7 @@ class RoomListFragment : Fragment(), ToolbarConfigurable {
private fun signOut() {
lifecycleScope.launch {
try {
- session.signOutService().signOut(true)
+ session.signOutService().signOut(signOutFromHomeserver = true, deleteCrypto = false)
} catch (failure: Throwable) {
activity?.let {
Toast.makeText(it, "Failure: $failure", Toast.LENGTH_SHORT).show()
@@ -186,4 +229,20 @@ class RoomListFragment : Fragment(), ToolbarConfigurable {
}
roomAdapter.setItems(sortedRoomSummaryList)
}
+
+ @SuppressLint("SetWorldReadable")
+ private fun saveFileToExternalStorage(fileName: String, data: ByteArray) {
+ val targetFile = File(e2eBackupDir, fileName)
+ targetFile.setReadable(true, false)
+ data.inputStream().use { input ->
+ FileOutputStream(targetFile).use { output ->
+ input.copyTo(output)
+ }
+ }
+ }
+
+ private fun readFileFromExternalStorage(fileName: String): ByteArray {
+ val targetFile = File(e2eBackupDir, fileName)
+ return targetFile.readBytes()
+ }
}
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/ui/SimpleLoginFragment.kt b/app/src/main/java/org/sdn/android/sdk/sample/ui/SimpleLoginFragment.kt
index 22360e5..3e939d8 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/ui/SimpleLoginFragment.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/ui/SimpleLoginFragment.kt
@@ -16,6 +16,7 @@
package org.sdn.android.sdk.sample.ui
+import android.content.Context.MODE_PRIVATE
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -106,8 +107,16 @@ class SimpleLoginFragment : Fragment() {
val ecKeyPair: ECKeyPair = ECKeyPair.create(privateKey.decodeHex().toByteArray())
val authService = SampleApp.getSDNClient(requireContext()).authenticationService()
+ val sp = requireContext().getSharedPreferences("device_data", MODE_PRIVATE)
+ var deviceIdKey = "device_id_$address"
+ var deviceId = ""
try {
- val loginDidMsg = authService.didPreLogin(edgeNodeConnectionConfig, address)
+ val fedInfo = authService.getFedInfo(edgeNodeConnectionConfig)
+ edgeNodeConnectionConfig.peerId = fedInfo.peer
+ deviceIdKey = "device_id_${fedInfo.peer}_$address"
+ deviceId = sp.getString(deviceIdKey, "") ?: ""
+
+ val loginDidMsg = authService.didPreLogin(edgeNodeConnectionConfig, address, deviceId)
if (loginDidMsg.message is String) {
Log.d("loginLoginDidMsg", loginDidMsg.message)
}
@@ -127,6 +136,11 @@ class SimpleLoginFragment : Fragment() {
Toast.makeText(requireContext(), "Failure: $failure", Toast.LENGTH_SHORT).show()
null
}?.let {
+ val retDeviceId = it.sessionParams.deviceId
+ if (retDeviceId != deviceId) {
+ Timber.tag("login").i("get new device id: $retDeviceId")
+ sp.edit().putString(deviceIdKey, retDeviceId).apply()
+ }
SessionHolder.currentSession = it
it.open()
it.syncService().startSync(true)
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/ui/dialog/PasswordDialogFragment.kt b/app/src/main/java/org/sdn/android/sdk/sample/ui/dialog/PasswordDialogFragment.kt
new file mode 100644
index 0000000..e51e57a
--- /dev/null
+++ b/app/src/main/java/org/sdn/android/sdk/sample/ui/dialog/PasswordDialogFragment.kt
@@ -0,0 +1,63 @@
+package org.sdn.android.sdk.sample.ui.dialog
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.EditText
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import org.sdn.android.sdk.sample.R
+import org.sdn.android.sdk.sample.databinding.FragmentDlgPasswordBinding
+
+class PasswordDialogFragment : DialogFragment() {
+
+ private var _views: FragmentDlgPasswordBinding? = null
+ private val views get() = _views!!
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(STYLE_NORMAL, 0)
+ }
+
+ override fun onStart() {
+ val params = dialog!!.window!!.attributes
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT
+ dialog!!.window!!.attributes = params as WindowManager.LayoutParams
+ super.onStart()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _views = FragmentDlgPasswordBinding.inflate(inflater, container, false)
+ return views.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ val inputPassword: EditText = view.findViewById(R.id.input_password)
+ inputPassword.requestFocus()
+ dialog!!.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
+ super.onViewCreated(view, savedInstanceState)
+
+ val requestKey = arguments?.getString("requestKey") ?: ""
+ views.btnOk.setOnClickListener {
+ val password = views.inputPassword.text.toString()
+ parentFragmentManager.setFragmentResult(requestKey, bundleOf(Pair("password", password)))
+ dismiss()
+ }
+ views.btnCancel.setOnClickListener {
+ dismiss()
+ }
+ }
+
+ companion object {
+ fun newInstance(requestKey: String): PasswordDialogFragment {
+ // Supply num input as an argument.
+ val args = Bundle().also { it.putString("requestKey", requestKey) }
+ return PasswordDialogFragment().also { it.arguments = args }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/utils/AvatarRenderer.kt b/app/src/main/java/org/sdn/android/sdk/sample/utils/AvatarRenderer.kt
index 847546e..8824cb4 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/utils/AvatarRenderer.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/utils/AvatarRenderer.kt
@@ -41,6 +41,27 @@ class AvatarRenderer(private val frag: Fragment, private val sdnItemColorProvide
GlideApp.with(frag).load(resolvedUrl).fallback(R.drawable.default_avatar).into(imageView)
}
+ fun renderDrawable(letter: String?, imageView: ImageView) {
+
+ if (letter?.startsWith("http") == true) {
+ render(letter, imageView)
+ return
+ }
+
+ val color = sdnItemColorProvider.getColorForRoom(letter ?: "")
+ val drawable = TextDrawable.builder()
+ .beginConfig()
+ .bold()
+ .endConfig()
+ .buildRoundRect(letter, color, 5)
+ val nullStr: String? = null
+ Picasso.get()
+ .load(nullStr)
+ .placeholder(drawable)
+ .error(R.drawable.default_avatar)
+ .into(imageView)
+ }
+
fun render(SDNItem: SDNItem, imageView: ImageView) {
val resolvedUrl = resolvedUrl(SDNItem.avatarUrl)
val placeholder = getPlaceholderDrawable(SDNItem)
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/utils/MatrixItemColorProvider.kt b/app/src/main/java/org/sdn/android/sdk/sample/utils/MatrixItemColorProvider.kt
index 6047f10..fcad82b 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/utils/MatrixItemColorProvider.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/utils/MatrixItemColorProvider.kt
@@ -41,6 +41,15 @@ class SDNItemColorProvider(private val context: Context) {
}
}
+ fun getColorForRoom(id: String): Int {
+ return cache.getOrPut(id) {
+ ContextCompat.getColor(
+ context,
+ getColorFromRoomId(id)
+ )
+ }
+ }
+
companion object {
@ColorRes
@VisibleForTesting
@@ -63,10 +72,13 @@ class SDNItemColorProvider(private val context: Context) {
@ColorRes
private fun getColorFromRoomId(roomId: String?): Int {
- return when ((roomId?.toList()?.sumOf { it.code } ?: 0) % 3) {
- 1 -> R.color.avatar_fill_2
- 2 -> R.color.avatar_fill_3
- else -> R.color.avatar_fill_1
+ return when ((roomId?.toList()?.sumOf { it.code } ?: 0) % 6) {
+ 1 -> R.color.avatar_fill_1
+ 2 -> R.color.avatar_fill_2
+ 3 -> R.color.avatar_fill_3
+ 4 -> R.color.avatar_fill_4
+ 5 -> R.color.avatar_fill_5
+ else -> R.color.avatar_fill_6
}
}
}
diff --git a/app/src/main/java/org/sdn/android/sdk/sample/utils/TimelineEventListProcessor.kt b/app/src/main/java/org/sdn/android/sdk/sample/utils/TimelineEventListProcessor.kt
index ffbea6a..6d2d0e2 100644
--- a/app/src/main/java/org/sdn/android/sdk/sample/utils/TimelineEventListProcessor.kt
+++ b/app/src/main/java/org/sdn/android/sdk/sample/utils/TimelineEventListProcessor.kt
@@ -48,6 +48,7 @@ class TimelineEventListProcessor(private val adapter: MessagesListAdapter
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_room_detail.xml b/app/src/main/res/layout/fragment_room_detail.xml
index c2b5401..bd93209 100644
--- a/app/src/main/res/layout/fragment_room_detail.xml
+++ b/app/src/main/res/layout/fragment_room_detail.xml
@@ -26,20 +26,9 @@
android:layout_centerVertical="true"
tools:src="@tools:sample/avatars" />
-
-
+
+
+ android:title="@string/logout"
+ app:showAsAction="never" />
\ No newline at end of file
diff --git a/app/src/main/res/menu/room_detail_options.xml b/app/src/main/res/menu/room_detail_options.xml
new file mode 100644
index 0000000..70efb17
--- /dev/null
+++ b/app/src/main/res/menu/room_detail_options.xml
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ad4bf40..a004908 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -10,6 +10,9 @@
#FF03B381
#FF368bd6
#FFac3ba8
+ #FF03B381
+ #FF2dc2c5
+ #FF74d12c
#368bd6
#ac3ba8
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 53b6c82..e5fca93 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,4 +1,14 @@
-
+
sdn-sdk-android-sample
Conference
+ Import Key
+ Export Key
+ Logout
+ Password
+ Cancel
+ OK
+ Leave
+ Invite
+ Meeting
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 61c08a3..47cf766 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -7,6 +7,7 @@
- @color/colorAccent
- #FFFFFF
- @style/ToolbarStyle
+ - @style/AlertDialogTheme
- true
@@ -17,5 +18,16 @@
- 0dp
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
index 4d1d55f..176d19a 100644
--- a/app/src/main/res/xml/network_security_config.xml
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -1,6 +1,4 @@
-
- 10.1.5.7
-
+
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 31c32bb..a90f0b1 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,5 +1,5 @@
ext.versions = [
- 'minSdk' : 21,
+ 'minSdk' : 23,
'compileSdk' : 33,
'targetSdk' : 33,
'sourceCompat' : JavaVersion.VERSION_11,
diff --git a/gradle.properties b/gradle.properties
index 7960af3..1ab53d8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,7 +20,7 @@ android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
-vector.debugPrivateData=false
+vector.debugPrivateData=true
vector.httpLogLevel=NONE
android.experimental.legacyTransform.forceNonIncremental=true
\ No newline at end of file
diff --git a/sdn-sdk-android/build.gradle b/sdn-sdk-android/build.gradle
index a4d2a6e..7153b25 100644
--- a/sdn-sdk-android/build.gradle
+++ b/sdn-sdk-android/build.gradle
@@ -196,7 +196,8 @@ dependencies {
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
- kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
+// kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
+ kapt fileTree(dir: 'libs', include: ['*.jar'])
// Shared Preferences
implementation libs.androidx.preferenceKtx
diff --git a/sdn-sdk-android/libs/realmfieldnameshelper-2.0.0.jar b/sdn-sdk-android/libs/realmfieldnameshelper-2.0.0.jar
new file mode 100644
index 0000000..f35caaa
Binary files /dev/null and b/sdn-sdk-android/libs/realmfieldnameshelper-2.0.0.jar differ
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/AuthenticationService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/AuthenticationService.kt
index 286e408..9af21ff 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/AuthenticationService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/AuthenticationService.kt
@@ -143,9 +143,14 @@ interface AuthenticationService {
updated: String,
): DidSaveResp
+ suspend fun getFedInfo(
+ edgeNodeConnectionConfig: EdgeNodeConnectionConfig,
+ ): FedInfoResp
+
suspend fun didPreLogin(
edgeNodeConnectionConfig: EdgeNodeConnectionConfig,
address: String,
+ deviceId: String = ""
): LoginDidMsg
suspend fun didLogin(
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/Credentials.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/Credentials.kt
index 5b7bfb1..a466e34 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/Credentials.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/Credentials.kt
@@ -55,9 +55,15 @@ data class Credentials(
* reconfigure themselves, optionally validating the URLs within.
* This object takes the same form as the one returned from .well-known autodiscovery.
*/
- @Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null
+ @Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null,
+
+ @Json(name = "login_time") var loginTime: Long = 0,
+
+ @Json(name = "peer_id") var peerId: String = "",
)
internal fun Credentials.sessionId(): String {
- return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5()
+ val userDevice = if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId"
+ val userDevicePeer = if (peerId.isBlank()) userDevice else "$userDevice|$peerId"
+ return userDevicePeer.md5()
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/EdgeNodeConnectionConfig.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/EdgeNodeConnectionConfig.kt
index 80c8f6d..6e94760 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/EdgeNodeConnectionConfig.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/EdgeNodeConnectionConfig.kt
@@ -46,7 +46,8 @@ data class EdgeNodeConnectionConfig(
val tlsCipherSuites: List? = null,
val shouldAcceptTlsExtensions: Boolean = true,
val allowHttpExtension: Boolean = false,
- val forceUsageTlsVersions: Boolean = false
+ val forceUsageTlsVersions: Boolean = false,
+ var peerId: String = ""
) {
/**
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/FedInfoResp.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/FedInfoResp.kt
new file mode 100644
index 0000000..fdeda54
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/auth/data/FedInfoResp.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.sdn.android.sdk.api.auth.data
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class FedInfoResp(
+
+ @Json(name = "peer")
+ val peer: String,
+
+ @Json(name = "addrs")
+ val addrs: List?
+)
\ No newline at end of file
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/CryptoConstants.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/CryptoConstants.kt
index 86dec57..b21a51d 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/CryptoConstants.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/CryptoConstants.kt
@@ -17,17 +17,22 @@
package org.sdn.android.sdk.api.crypto
/**
- * Matrix algorithm value for olm.
+ * Algorithm value for olm.
*/
const val MXCRYPTO_ALGORITHM_OLM = "m.olm.v1.curve25519-aes-sha2"
/**
- * Matrix algorithm value for megolm.
+ * Algorithm value for megolm.
*/
const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
/**
- * Matrix algorithm value for megolm keys backup.
+ * Algorithm value for ratchet.
+ */
+const val MXCRYPTO_ALGORITHM_RATCHET = "m.megolm.v1.aes-ratchet"
+
+/**
+ * Algorithm value for megolm keys backup.
*/
const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/MXCryptoConfig.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/MXCryptoConfig.kt
index a45d269..49aa6ee 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/MXCryptoConfig.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/crypto/MXCryptoConfig.kt
@@ -38,6 +38,6 @@ data class MXCryptoConfig constructor(
* You can limit request only to your sessions by turning this setting to `true`.
* Forwarded keys coming from other users will also be ignored if set to true.
*/
- val limitRoomKeyRequestsToMyDevices: Boolean = true,
+ val limitRoomKeyRequestsToMyDevices: Boolean = false,
)
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/CryptoService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/CryptoService.kt
index c8bfd25..557ae5f 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/CryptoService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/CryptoService.kt
@@ -170,6 +170,8 @@ interface CryptoService {
fun decryptEventAsync(event: Event, timeline: String, callback: SDNCallback)
+ fun getRoomUserIds(roomId: String): List
+
fun getEncryptionAlgorithm(roomId: String): String?
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt
index 5f6622e..24f2a8d 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt
@@ -23,7 +23,7 @@ data class CryptoDeviceInfo(
val deviceId: String,
override val userId: String,
val algorithms: List? = null,
- override val keys: Map? = null,
+ override var keys: Map? = null,
override val signatures: Map>? = null,
val unsigned: UnsignedDeviceInfo? = null,
var trustLevel: DeviceTrustLevel? = null,
@@ -55,6 +55,18 @@ data class CryptoDeviceInfo(
?.get("curve25519:$deviceId")
}
+ fun fallbackKey(): String? {
+ return keys
+ ?.takeIf { deviceId.isNotBlank() }
+ ?.get("fbk25519:$deviceId")
+ }
+
+ fun setFallbackKey(fbk: String) {
+ val keyMap = keys?.toMutableMap()
+ keyMap?.put("fbk25519:$deviceId", fbk)
+ keys = keyMap
+ }
+
/**
* @return the display name
*/
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RequestRecipient.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RequestRecipient.kt
new file mode 100644
index 0000000..9894bc4
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RequestRecipient.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.sdn.android.sdk.api.session.crypto.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.sdn.android.sdk.internal.di.MoshiProvider
+
+/**
+ * Class representing an room key request recipient.
+ */
+@JsonClass(generateAdapter = true)
+data class RequestRecipient(
+ @Json(name = "userId")
+ val userId: String,
+
+ @Json(name = "deviceId")
+ val deviceId: String,
+) {
+ fun toJson(): String {
+ return MoshiProvider.providesMoshi().adapter(RequestRecipient::class.java).toJson(this)
+ }
+
+ companion object {
+ fun fromJson(json: String?): RequestRecipient? {
+ return json?.let { MoshiProvider.providesMoshi().adapter(RequestRecipient::class.java).fromJson(it) }
+ }
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt
index 72f9a0c..f428240 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt
@@ -30,6 +30,15 @@ data class RoomKeyShareRequest(
@Json(name = "requesting_device_id")
override val requestingDeviceId: String? = null,
+ @Json(name = "requesting_device_key")
+ val requestingDeviceKey: String? = null,
+
+ @Json(name = "requesting_device_otk")
+ val requestingDeviceOtk: String? = null,
+
+ @Json(name = "recipients")
+ var recipients: List? = null,
+
@Json(name = "request_id")
override val requestId: String? = null,
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/events/model/EventType.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/events/model/EventType.kt
index 6eb76a4..1b4679f 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/events/model/EventType.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/events/model/EventType.kt
@@ -88,6 +88,8 @@ object EventType {
// Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
+ const val ROOM_KEY_REQUIRE = "m.room_key_require"
+ const val ROOM_KEY_REPLY = "m.room_key_reply"
val ROOM_KEY_WITHHELD = StableUnstableId(stable = "m.room_key.withheld", unstable = "org.matrix.room_key.withheld")
const val REQUEST_SECRET = "m.secret.request"
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt
index 5f53df3..bcda59a 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt
@@ -17,12 +17,14 @@
package org.sdn.android.sdk.api.session.room.model
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
sealed class RoomEncryptionAlgorithm {
abstract class SupportedAlgorithm(val alg: String) : RoomEncryptionAlgorithm()
object Megolm : SupportedAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM)
+ object Ratchet : SupportedAlgorithm(MXCRYPTO_ALGORITHM_RATCHET)
data class UnsupportedAlgorithm(val name: String?) : RoomEncryptionAlgorithm()
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/signout/SignOutService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/signout/SignOutService.kt
index 9a235eb..cd60052 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/signout/SignOutService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/signout/SignOutService.kt
@@ -38,5 +38,5 @@ interface SignOutService {
* Sign out, and release the session, clear all the session data, including crypto data.
* @param signOutFromHomeserver true if the sign out request has to be done
*/
- suspend fun signOut(signOutFromHomeserver: Boolean)
+ suspend fun signOut(signOutFromHomeserver: Boolean, deleteCrypto: Boolean = false)
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthAPI.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthAPI.kt
index 63e72cd..adbf211 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthAPI.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthAPI.kt
@@ -120,6 +120,9 @@ internal interface AuthAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
suspend fun getLoginFlows(): LoginFlowResponse
+ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "fedinfo")
+ suspend fun fedInfo(): FedInfoResp
+
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "address/{address}")
suspend fun didList(@Path("address") address: String): DidListResp
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthModule.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthModule.kt
index e31cc36..850446a 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthModule.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/AuthModule.kt
@@ -102,6 +102,9 @@ internal abstract class AuthModule {
@Binds
abstract fun bindQrLoginTokenTask(task: DefaultQrLoginTokenTask): QrLoginTokenTask
+ @Binds
+ abstract fun bindFedInfoTask(task: DefaultFedInfoTask): FedInfoTask
+
@Binds
abstract fun bindDidListTask(task: DefaultDidListTask): DidListTask
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/DefaultAuthenticationService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/DefaultAuthenticationService.kt
index c5942a8..0d6bbeb 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -70,7 +70,8 @@ internal class DefaultAuthenticationService @Inject constructor(
private val didCreateTask: DidCreateTask,
private val didSaveTask: DidSaveTask,
private val didPreLoginTask: DidPreLoginTask,
- private val didLoginTask: DidLoginTask
+ private val didLoginTask: DidLoginTask,
+ private val fedInfoTask: FedInfoTask
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
@@ -458,14 +459,24 @@ internal class DefaultAuthenticationService @Inject constructor(
)
}
+ override suspend fun getFedInfo(
+ edgeNodeConnectionConfig: EdgeNodeConnectionConfig,
+ ): FedInfoResp {
+ return fedInfoTask.execute(
+ FedInfoTask.Params(edgeNodeConnectionConfig = edgeNodeConnectionConfig)
+ )
+ }
+
override suspend fun didPreLogin(
edgeNodeConnectionConfig: EdgeNodeConnectionConfig,
address: String,
+ deviceId: String
): LoginDidMsg {
return didPreLoginTask.execute(
DidPreLoginTask.Params(
edgeNodeConnectionConfig = edgeNodeConnectionConfig,
- address = address
+ address = address,
+ deviceId = deviceId
)
)
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/data/PreLoginParams.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/data/PreLoginParams.kt
index 0803a7d..bd11ba2 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/data/PreLoginParams.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/data/PreLoginParams.kt
@@ -23,4 +23,5 @@ import com.squareup.moshi.JsonClass
internal data class PreLoginParams(
@Json(name = "address") val address: String,
@Json(name = "did") val did: String,
+ @Json(name = "device_id") val deviceId: String,
)
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidLoginTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidLoginTask.kt
index ba89dcd..29661ce 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidLoginTask.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidLoginTask.kt
@@ -89,6 +89,8 @@ internal class DefaultDidLoginTask @Inject constructor(
}
}
+ credentials.loginTime = System.currentTimeMillis()
+ credentials.peerId = params.edgeNodeConnectionConfig.peerId
return sessionCreator.createSession(credentials, params.edgeNodeConnectionConfig, LoginType.DIRECT)
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidPreLoginTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidPreLoginTask.kt
index af023e2..ecd8d27 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidPreLoginTask.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/DidPreLoginTask.kt
@@ -34,7 +34,8 @@ import javax.inject.Inject
internal interface DidPreLoginTask : Task {
data class Params(
val edgeNodeConnectionConfig: EdgeNodeConnectionConfig,
- val address: String
+ val address: String,
+ val deviceId: String
)
}
@@ -57,9 +58,9 @@ internal class DefaultDidPreLoginTask @Inject constructor(
authAPI.didList(params.address)
}
val loginParams = if (didListResp.data.isNotEmpty()) {
- PreLoginParams("", didListResp.data[0])
+ PreLoginParams("", didListResp.data[0], params.deviceId)
} else {
- PreLoginParams(params.address, "")
+ PreLoginParams(params.address, "", params.deviceId)
}
executeRequest(null) {
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/FedInfoTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/FedInfoTask.kt
new file mode 100644
index 0000000..f8bfb8f
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/auth/login/FedInfoTask.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.sdn.android.sdk.internal.auth.login
+
+import dagger.Lazy
+import okhttp3.OkHttpClient
+import org.sdn.android.sdk.api.auth.data.EdgeNodeConnectionConfig
+import org.sdn.android.sdk.api.auth.data.FedInfoResp
+import org.sdn.android.sdk.api.failure.Failure
+import org.sdn.android.sdk.internal.auth.AuthAPI
+import org.sdn.android.sdk.internal.di.Unauthenticated
+import org.sdn.android.sdk.internal.network.RetrofitFactory
+import org.sdn.android.sdk.internal.network.executeRequest
+import org.sdn.android.sdk.internal.network.httpclient.addSocketFactory
+import org.sdn.android.sdk.internal.network.ssl.UnrecognizedCertificateException
+import org.sdn.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface FedInfoTask : Task {
+ data class Params(
+ val edgeNodeConnectionConfig: EdgeNodeConnectionConfig
+ )
+}
+
+internal class DefaultFedInfoTask @Inject constructor(
+ @Unauthenticated
+ private val okHttpClient: Lazy,
+ private val retrofitFactory: RetrofitFactory,
+) : FedInfoTask {
+
+ override suspend fun execute(params: FedInfoTask.Params): FedInfoResp {
+ val client = buildClient(params.edgeNodeConnectionConfig)
+ val homeServerUrl = params.edgeNodeConnectionConfig.homeServerUriBase.toString()
+
+ val authAPI = retrofitFactory.create(client, homeServerUrl)
+ .create(AuthAPI::class.java)
+
+ val fedInfoResp = try {
+ executeRequest(null) {
+ authAPI.fedInfo()
+ }
+ } catch (throwable: Throwable) {
+ throw when (throwable) {
+ is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure(
+ homeServerUrl,
+ throwable.fingerprint
+ )
+ else -> throwable
+ }
+ }
+
+ return fedInfoResp
+ }
+
+ private fun buildClient(edgeNodeConnectionConfig: EdgeNodeConnectionConfig): OkHttpClient {
+ return okHttpClient.get()
+ .newBuilder()
+ .addSocketFactory(edgeNodeConnectionConfig)
+ .build()
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/CryptoModule.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/CryptoModule.kt
index 08fd211..5074ce6 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/CryptoModule.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/CryptoModule.kt
@@ -68,8 +68,12 @@ import org.sdn.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
import org.sdn.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultGetDevicesTask
+import org.sdn.android.sdk.internal.crypto.tasks.DefaultGetSessionMapTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultInitializeCrossSigningTask
+import org.sdn.android.sdk.internal.crypto.tasks.DefaultPullSessionKeysTask
+import org.sdn.android.sdk.internal.crypto.tasks.DefaultPutSessionMapTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendEventTask
+import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendSimpleEventTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSetDeviceNameTask
@@ -81,8 +85,12 @@ import org.sdn.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.sdn.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.sdn.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.sdn.android.sdk.internal.crypto.tasks.GetDevicesTask
+import org.sdn.android.sdk.internal.crypto.tasks.GetSessionMapTask
import org.sdn.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
+import org.sdn.android.sdk.internal.crypto.tasks.PullSessionKeysTask
+import org.sdn.android.sdk.internal.crypto.tasks.PutSessionMapTask
import org.sdn.android.sdk.internal.crypto.tasks.SendEventTask
+import org.sdn.android.sdk.internal.crypto.tasks.SendSimpleEventTask
import org.sdn.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.sdn.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.sdn.android.sdk.internal.crypto.tasks.SetDeviceNameTask
@@ -230,6 +238,18 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
+ @Binds
+ abstract fun bindSendSimpleEventTask(task: DefaultSendSimpleEventTask): SendSimpleEventTask
+
+ @Binds
+ abstract fun bindPullRoomKeyTask(task: DefaultPullSessionKeysTask): PullSessionKeysTask
+
+ @Binds
+ abstract fun bindGetSessionMapTask(task: DefaultGetSessionMapTask): GetSessionMapTask
+
+ @Binds
+ abstract fun bindPutSessionMapTask(task: DefaultPutSessionMapTask): PutSessionMapTask
+
@Binds
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DefaultCryptoService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DefaultCryptoService.kt
index dba9d0c..71975ae 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -25,6 +25,7 @@ import dagger.Lazy
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -32,6 +33,7 @@ import org.sdn.android.sdk.api.SDNCallback
import org.sdn.android.sdk.api.SDNCoroutineDispatchers
import org.sdn.android.sdk.api.NoOpSDNCallback
import org.sdn.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
import org.sdn.android.sdk.api.crypto.MXCryptoConfig
@@ -79,6 +81,7 @@ import org.sdn.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
import org.sdn.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.sdn.android.sdk.internal.crypto.algorithms.IMXEncrypting
import org.sdn.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
+import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXRatchetEncryptionFactory
import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
import org.sdn.android.sdk.internal.crypto.algorithms.megolm.UnRequestedForwardManager
import org.sdn.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
@@ -110,6 +113,14 @@ import org.sdn.android.sdk.internal.task.launchToCallback
import org.sdn.android.sdk.internal.util.JsonCanonicalizer
import org.sdn.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmManager
+import org.sdn.android.sdk.api.session.crypto.model.OlmDecryptionResult
+import org.sdn.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.sdn.android.sdk.api.session.events.model.content.EncryptedEventContent
+import org.sdn.android.sdk.api.session.events.model.content.OlmEventContent
+import org.sdn.android.sdk.api.session.room.model.message.MessageContent
+import org.sdn.android.sdk.api.session.sync.model.ToDeviceSyncResponse
+import org.sdn.android.sdk.internal.crypto.model.rest.EncryptedMessage
+import org.sdn.android.sdk.internal.crypto.tasks.PullSessionKeysTask
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@@ -170,9 +181,11 @@ internal class DefaultCryptoService @Inject constructor(
private val megolmSessionDataImporter: MegolmSessionDataImporter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
// Repository
+ private val ratchetEncryptionFactory: MXRatchetEncryptionFactory,
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
private val olmEncryptionFactory: MXOlmEncryptionFactory,
// Tasks
+ private val pullSessionKeysTask: PullSessionKeysTask,
private val deleteDeviceTask: DeleteDeviceTask,
private val getDevicesTask: GetDevicesTask,
private val getDeviceInfoTask: GetDeviceInfoTask,
@@ -201,6 +214,17 @@ internal class DefaultCryptoService @Inject constructor(
}
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
+ val senderUserId = event.senderId
+ val senderDeviceId = event.content?.get("device_id")?.toString()
+ val senderDeviceKey = event.getSenderKey()
+ if (!senderUserId.isNullOrEmpty() && !senderDeviceId.isNullOrEmpty() && !senderDeviceKey.isNullOrEmpty()) {
+ val deviceInfo = this.deviceListManager.getUserDevice(senderUserId, senderDeviceId)
+ if (deviceInfo == null || deviceInfo.identityKey() != senderDeviceKey) {
+ Timber.tag(loggerTag.value).w("unknown sender $senderUserId | $senderDeviceId from event ${event.eventId}")
+ this.deviceListManager.handleDeviceListsChanges(listOf(senderUserId), emptyList())
+ }
+ }
+
// handle state events
if (event.isStateEvent()) {
when (event.type) {
@@ -513,7 +537,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return the device info, or null if not found / unsupported algorithm / crypto released
*/
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo? {
- return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
+ return if (algorithm != MXCRYPTO_ALGORITHM_RATCHET && algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
// We only deal in olm keys
null
} else cryptoStore.deviceWithIdentityKey(senderKey)
@@ -646,13 +670,14 @@ internal class DefaultCryptoService @Inject constructor(
}
val alg: IMXEncrypting? = when (algorithm) {
+ MXCRYPTO_ALGORITHM_RATCHET -> ratchetEncryptionFactory.create(roomId)
MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId)
else -> null
}
if (alg != null) {
- roomEncryptorsStore.put(roomId, alg)
+ roomEncryptorsStore.put(roomId, algorithm!!, alg)
}
// if encryption was not previously enabled in this room, we will have been
@@ -665,7 +690,11 @@ internal class DefaultCryptoService @Inject constructor(
val userIds = ArrayList(membersId)
- deviceListManager.startTrackingDeviceList(userIds)
+ if (userIds.size > 200) {
+ Timber.w("skip tracking devices for room $roomId with ${userIds.size} members")
+ } else {
+ deviceListManager.startTrackingDeviceList(userIds)
+ }
if (!inhibitDeviceQuery) {
deviceListManager.refreshOutdatedDeviceLists()
@@ -676,10 +705,10 @@ internal class DefaultCryptoService @Inject constructor(
}
/**
- * Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM.
+ * Tells if a room is encrypted.
*
* @param roomId the room id
- * @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
+ * @return true if the room is encrypted
*/
override fun isRoomEncrypted(roomId: String): Boolean {
return cryptoSessionInfoProvider.isRoomEncrypted(roomId)
@@ -729,7 +758,9 @@ internal class DefaultCryptoService @Inject constructor(
// moved to crypto scope to have uptodate values
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val userIds = getRoomUserIds(roomId)
- var alg = roomEncryptorsStore.get(roomId)
+ val memberNumThreshold = 200
+ val algId = if (userIds.count() > memberNumThreshold) MXCRYPTO_ALGORITHM_RATCHET else MXCRYPTO_ALGORITHM_MEGOLM
+ var alg = roomEncryptorsStore.get(roomId, algId)
if (alg == null) {
val algorithm = getEncryptionAlgorithm(roomId)
if (algorithm != null) {
@@ -738,6 +769,7 @@ internal class DefaultCryptoService @Inject constructor(
}
}
}
+
val safeAlgorithm = alg
if (safeAlgorithm != null) {
val t0 = clock.epochMillis()
@@ -779,7 +811,20 @@ internal class DefaultCryptoService @Inject constructor(
*/
@Throws(MXCryptoError::class)
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
- return internalDecryptEvent(event, timeline)
+ try {
+ return internalDecryptEvent(event, timeline)
+ } catch (exception: MXCryptoError) {
+ if (exception is MXCryptoError.Base) {
+ if (exception.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
+ val algorithm = event.content?.get("algorithm")?.toString()
+ if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM || algorithm == MXCRYPTO_ALGORITHM_RATCHET) {
+ pullRoomKeyForEvent(event)
+ }
+ }
+ }
+
+ throw exception
+ }
}
/**
@@ -825,7 +870,7 @@ internal class DefaultCryptoService @Inject constructor(
when (event.getClearType()) {
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
// Keys are imported directly, not waiting for end of sync
- onRoomKeyEvent(event)
+ onRoomKeyEvent(event, true)
}
EventType.REQUEST_SECRET -> {
secretShareManager.handleSecretRequest(event)
@@ -838,7 +883,10 @@ internal class DefaultCryptoService @Inject constructor(
// very silly, it won't work because an Olm session cannot send
// messages to itself.
if (req.requestingDeviceId != deviceId) { // ignore self requests
- event.senderId?.let { incomingKeyRequestManager.addNewIncomingRequest(it, req) }
+ event.senderId?.let {
+ ensureRequestingUserDevice(it, req.requestingDeviceId)
+ incomingKeyRequestManager.addNewIncomingRequest(it, req)
+ }
}
}
}
@@ -856,6 +904,20 @@ internal class DefaultCryptoService @Inject constructor(
liveEventManager.get().dispatchOnLiveToDevice(event)
}
+ private suspend fun ensureRequestingUserDevice(userId: String?, deviceId: String?) : CryptoDeviceInfo? {
+ if (userId == null || deviceId == null) {
+ return null
+ }
+ var requestingDevice = cryptoStore.getUserDevice(userId, deviceId)
+ if (requestingDevice == null) {
+ Timber.tag(loggerTag.value).w("ensureRequestingUserDevice() : Unknown device: $userId | $deviceId")
+
+ val deviceMap = deviceListManager.downloadKeys(listOf(userId), true)
+ requestingDevice = deviceMap.getObject(userId, deviceId)
+ }
+ return requestingDevice
+ }
+
/**
* Handle a key event.
*
@@ -955,7 +1017,7 @@ internal class DefaultCryptoService @Inject constructor(
}
}
- private fun getRoomUserIds(roomId: String): List {
+ override fun getRoomUserIds(roomId: String): List {
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() &&
shouldEncryptForInvitedMembers(roomId)
return cryptoSessionInfoProvider.getRoomUserIds(roomId, encryptForInvitedMembers)
@@ -1014,10 +1076,6 @@ internal class DefaultCryptoService @Inject constructor(
* Upload my user's device keys.
*/
private suspend fun uploadDeviceKeys() {
- if (cryptoStore.areDeviceKeysUploaded()) {
- Timber.tag(loggerTag.value).d("Keys already uploaded, nothing to do")
- return
- }
// Prepare the device keys data to send
// Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
@@ -1220,10 +1278,116 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun reRequestRoomKeyForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, true)
+ pullRoomKeyForEvent(event)
}
override fun requestRoomKeyForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, false)
+ pullRoomKeyForEvent(event)
+ }
+
+ private fun pullRoomKeyForEvent(event: Event) {
+ if (event.originServerTs != null && event.originServerTs < deviceListManager.getSessionThresholdTime()) {
+ Timber.i("skip pulling keys for old event: ${event.eventId}")
+ return
+ }
+
+ val encryptedEventContent = event.content.toModel()
+ if (encryptedEventContent == null) {
+ Timber.tag(loggerTag.value).e("getRoomKeyRequestTargetForEvent Failed to re-request key, null content")
+ return
+ }
+
+ val requestBody = RoomKeyRequestBody(
+ roomId = event.roomId,
+ algorithm = encryptedEventContent.algorithm,
+ senderKey = encryptedEventContent.senderKey,
+ sessionId = encryptedEventContent.sessionId
+ )
+
+ cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
+ delay(5000L)
+ pullRoomKey(requestBody)
+ }
+ }
+
+ private fun pullRoomKey(requestBody: RoomKeyRequestBody) {
+ val roomId = requestBody.roomId ?: ""
+ val senderKey = requestBody.senderKey ?: ""
+ val sessionId = requestBody.sessionId ?: ""
+
+ if (olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)) {
+ Timber.i("skip pull room key for $sessionId")
+ return
+ }
+
+ val callback = object : SDNCallback {
+ override fun onFailure(failure: Throwable) {
+ Timber.e("pullRoomKey for $sessionId fail: $failure")
+ }
+
+ override fun onSuccess(data: ToDeviceSyncResponse) {
+ Timber.e("pullRoomKey for $sessionId return ${data.events?.size} events")
+ cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
+ data.events?.forEachIndexed { _, event ->
+ // Decrypt event if necessary
+ Timber.i("To device event from ${event.senderId} of type:${event.type}")
+ decryptToDeviceEvent(event, null)
+ if (event.getClearType() == EventType.MESSAGE &&
+ event.getClearContent()?.toModel()?.msgType == "m.bad.encrypted") {
+ Timber.e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
+ } else {
+ verificationService.onToDeviceEvent(event)
+ onToDeviceEvent(event)
+ }
+ }
+ }
+ }
+ }
+ pullSessionKeysTask
+ .configureWith(PullSessionKeysTask.Params(sessionId)) {
+ this.executionThread = TaskThread.CRYPTO
+ this.callback = callback
+ }
+ .executeBy(taskExecutor)
+ }
+
+ private suspend fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
+ Timber.v("## CRYPTO | decryptToDeviceEvent")
+ if (event.getClearType() == EventType.ENCRYPTED) {
+ var result: MXEventDecryptionResult? = null
+ try {
+ result = decryptEvent(event, timelineId ?: "")
+ } catch (exception: MXCryptoError) {
+ event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
+ val senderKey = event.content.toModel()?.senderKey ?: ""
+ // try to find device id to ease log reading
+ val deviceId = getCryptoDeviceInfo(event.senderId!!).firstOrNull {
+ it.identityKey() == senderKey
+ }?.deviceId ?: senderKey
+ Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
+ } catch (failure: Throwable) {
+ Timber.e(failure, "## CRYPTO | Failed to decrypt to device event from ${event.senderId}")
+ }
+
+ if (null != result) {
+ event.mxDecryptionResult = OlmDecryptionResult(
+ payload = result.clearEvent,
+ senderKey = result.senderCurve25519Key,
+ keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
+ forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+ isSafe = result.isSafe
+ )
+ return true
+ } else {
+ // Could happen for to device events
+ // None of the known session could decrypt the message
+ // In this case unwedging process might have been started (rate limited)
+ Timber.e("## CRYPTO | ERROR NULL DECRYPTION RESULT from ${event.senderId}")
+ }
+ }
+
+ return false
}
/**
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DeviceListManager.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DeviceListManager.kt
index 2cf7bb5..19468b5 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DeviceListManager.kt
@@ -43,7 +43,7 @@ import javax.inject.Inject
@SessionScope
internal class DeviceListManager @Inject constructor(
private val cryptoStore: IMXCryptoStore,
- private val olmDevice: MXOlmDevice,
+ val olmDevice: MXOlmDevice,
private val syncTokenStore: SyncTokenStore,
private val credentials: Credentials,
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
@@ -74,6 +74,18 @@ internal class DeviceListManager @Inject constructor(
}
}
+ fun getSessionLoginTime() : Long {
+ return credentials.loginTime
+ }
+
+ fun getSessionThresholdTime() : Long {
+ return credentials.loginTime - 24 * 3600 * 1000
+ }
+
+ fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
+ return this.cryptoStore.getUserDevice(userId, deviceId)
+ }
+
private fun dispatchDeviceChange(users: List) {
synchronized(deviceChangeListeners) {
deviceChangeListeners.forEach {
@@ -144,8 +156,12 @@ internal class DeviceListManager @Inject constructor(
if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) {
// It's OK to track also device for invited users
val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true)
- startTrackingDeviceList(userIds)
- refreshOutdatedDeviceLists()
+ if (userIds.size > 200) {
+ Timber.w("skip tracking devices for room $roomId with ${userIds.size} members")
+ } else {
+ startTrackingDeviceList(userIds)
+ refreshOutdatedDeviceLists()
+ }
}
}
}
@@ -445,8 +461,8 @@ internal class DeviceListManager @Inject constructor(
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
return false
}
-
- if (null == deviceKeys.keys) {
+ val keys = deviceKeys.keys
+ if (null == keys) {
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.keys is null from $userId:$deviceId")
return false
}
@@ -468,7 +484,7 @@ internal class DeviceListManager @Inject constructor(
}
val signKeyId = "ed25519:" + deviceKeys.deviceId
- val signKey = deviceKeys.keys[signKeyId]
+ val signKey = keys[signKeyId]
if (null == signKey) {
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
@@ -522,7 +538,8 @@ internal class DeviceListManager @Inject constructor(
Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
- return false
+ // log a warning and update the keys
+ return true
}
}
@@ -560,7 +577,6 @@ internal class DeviceListManager @Inject constructor(
}
)
}
-
companion object {
/**
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/EventDecryptor.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/EventDecryptor.kt
index ee023c6..21ef481 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/EventDecryptor.kt
@@ -141,7 +141,7 @@ internal class EventDecryptor @Inject constructor(
mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
// need to find sending device
val olmContent = event.content.toModel()
- if (event.senderId != null && olmContent?.senderKey != null) {
+ if (event.type != EventType.ROOM_KEY_REPLY && event.senderId != null && olmContent?.senderKey != null) {
markOlmSessionForUnwedging(event.senderId, olmContent.senderKey)
} else {
Timber.tag(loggerTag.value).d("Can't mark as wedge malformed")
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
index 943523b..4f8d67c 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
@@ -16,6 +16,7 @@
package org.sdn.android.sdk.internal.crypto
+import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
@@ -26,6 +27,7 @@ import kotlinx.coroutines.withContext
import org.sdn.android.sdk.api.SDNCoroutineDispatchers
import org.sdn.android.sdk.api.auth.data.Credentials
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.crypto.MXCryptoConfig
import org.sdn.android.sdk.api.extensions.tryOrNull
import org.sdn.android.sdk.api.logger.LoggerTag
@@ -82,9 +84,12 @@ internal class IncomingKeyRequestManager @Inject constructor(
val requestId: String,
val requestingUserId: String,
val requestingDeviceId: String,
+ val requestingDeviceKey: String?,
+ val requestingDeviceOtk: String?,
val roomId: String,
val senderKey: String,
val sessionId: String,
+ val algorithm: String,
val action: MegolmRequestAction
) {
fun shortDbgString() = "Request from $requestingUserId|$requestingDeviceId for session $sessionId in room $roomId"
@@ -97,7 +102,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
val sessionId = body.sessionId ?: return null
val senderKey = body.senderKey ?: return null
val requestId = this.requestId ?: return null
- if (body.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
+ if (!arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(body.algorithm)) return null
val action = when (this.action) {
"request" -> MegolmRequestAction.Request
"request_cancellation" -> MegolmRequestAction.Cancel
@@ -107,9 +112,12 @@ internal class IncomingKeyRequestManager @Inject constructor(
requestId = requestId,
requestingUserId = senderId,
requestingDeviceId = deviceId,
+ requestingDeviceKey = this.requestingDeviceKey,
+ requestingDeviceOtk = this.requestingDeviceOtk,
roomId = roomId,
senderKey = senderKey,
sessionId = sessionId,
+ algorithm = body.algorithm!!,
action = action
)
}
@@ -205,62 +213,28 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
private suspend fun handleIncomingRequest(request: ValidMegolmRequestBody) {
- // We don't want to download keys, if we don't know the device yet we won't share any how?
- val requestingDevice =
- cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId)
- ?: return Unit.also {
- Timber.tag(loggerTag.value).d("Ignoring key request: ${request.shortDbgString()}")
- }
-
- cryptoStore.saveIncomingKeyRequestAuditTrail(
- request.requestId,
- request.roomId,
- request.sessionId,
- request.senderKey,
- MXCRYPTO_ALGORITHM_MEGOLM,
- request.requestingUserId,
- request.requestingDeviceId
- )
-
- val roomAlgorithm = // withContext(coroutineDispatchers.crypto) {
- cryptoStore.getRoomAlgorithm(request.roomId)
-// }
- if (roomAlgorithm != MXCRYPTO_ALGORITHM_MEGOLM) {
+ if (request.requestingUserId == credentials.userId && request.requestingDeviceId == credentials.deviceId) {
+ Timber.tag(loggerTag.value).w("ignoring key request from own user")
+ return
+ }
+ if (!arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(request.algorithm)) {
// strange we received a request for a room that is not encrypted
// maybe a broken state?
- Timber.tag(loggerTag.value).w("Received a key request in a room with unsupported alg:$roomAlgorithm , req:${request.shortDbgString()}")
+ Timber.tag(loggerTag.value).w("Received a key request in a room with unsupported alg:${request.algorithm} , req:${request.shortDbgString()}")
return
}
-
- // Is it for one of our sessions?
- if (request.requestingUserId == credentials.userId) {
- Timber.tag(loggerTag.value).v("handling request from own user: megolm session ${request.sessionId}")
-
- if (request.requestingDeviceId == credentials.deviceId) {
- // ignore it's a remote echo
- return
- }
- // If it's verified we share from the early index we know
- // if not we check if it was originaly shared or not
- if (requestingDevice.isVerified) {
- // we share from the earliest known chain index
- shareMegolmKey(request, requestingDevice, null)
- } else {
- shareIfItWasPreviouslyShared(request, requestingDevice)
+ val requestingDevice = cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId)
+ if (requestingDevice != null) {
+ if (requestingDevice.fallbackKey().isNullOrEmpty() && !request.requestingDeviceOtk.isNullOrEmpty()) {
+ requestingDevice.setFallbackKey(request.requestingDeviceOtk)
}
- } else {
- if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
- Timber.tag(loggerTag.value).v("Ignore request from other user as per crypto config: ${request.shortDbgString()}")
- return
- }
- Timber.tag(loggerTag.value).v("handling request from other user: megolm session ${request.sessionId}")
- if (requestingDevice.isBlocked) {
- // it's blocked, so send a withheld code
- sendWithheldForRequest(request, WithHeldCode.BLACKLISTED)
- } else {
- shareIfItWasPreviouslyShared(request, requestingDevice)
+ if (!requestingDevice.fallbackKey().isNullOrEmpty()) {
+ if (shareMegolmKey(request, requestingDevice, null)){
+ return
+ }
}
}
+ directShareMegolmKey(request)
}
private suspend fun shareIfItWasPreviouslyShared(request: ValidMegolmRequestBody, requestingDevice: CryptoDeviceInfo) {
@@ -285,7 +259,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
deviceId = requestingDevice.deviceId,
requestId = request.requestId,
requestBody = RoomKeyRequestBody(
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ algorithm = request.algorithm,
senderKey = request.senderKey,
sessionId = request.sessionId,
roomId = request.roomId
@@ -308,7 +282,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
val withHeldContent = RoomKeyWithHeldContent(
roomId = request.roomId,
senderKey = request.senderKey,
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ algorithm = request.algorithm,
sessionId = request.sessionId,
codeString = code.value,
fromDevice = credentials.deviceId
@@ -331,7 +305,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
roomId = request.roomId,
sessionId = request.sessionId,
senderKey = request.senderKey,
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ algorithm = request.algorithm,
code = code,
userId = request.requestingUserId,
deviceId = request.requestingDeviceId
@@ -355,9 +329,12 @@ internal class IncomingKeyRequestManager @Inject constructor(
requestId = request.requestId,
requestingDeviceId = request.deviceId,
requestingUserId = request.userId,
+ requestingDeviceKey = null,
+ requestingDeviceOtk = null,
roomId = request.requestBody.roomId,
senderKey = request.requestBody.senderKey,
sessionId = request.requestBody.sessionId,
+ algorithm = request.requestBody.algorithm ?: "",
action = MegolmRequestAction.Request
)
val requestingDevice =
@@ -417,6 +394,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(requestingDevice))
+ encodedPayload.traceId = validRequest.sessionId
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(requestingDevice.userId, requestingDevice.deviceId, encodedPayload)
Timber.tag(loggerTag.value).d("reshareKey() : try sending session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
@@ -429,7 +407,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
validRequest.roomId,
validRequest.sessionId,
validRequest.senderKey,
- MXCRYPTO_ALGORITHM_MEGOLM,
+ validRequest.algorithm,
requestingDevice.userId,
requestingDevice.deviceId,
chainIndex
@@ -442,6 +420,58 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
}
+ private suspend fun directShareMegolmKey(validRequest: ValidMegolmRequestBody): Boolean {
+ val requestingDeviceKey = validRequest.requestingDeviceKey ?: return false.also {
+ Timber.tag(loggerTag.value).e("directShareMegolmKey: no requestingDeviceKey from ${validRequest.requestingUserId} | ${validRequest.requestingDeviceId}")
+ }
+ val requestingDeviceOtk = validRequest.requestingDeviceOtk ?: return false.also {
+ Timber.tag(loggerTag.value).e("directShareMegolmKey: no requestingDeviceOtk from ${validRequest.requestingUserId} | ${validRequest.requestingDeviceId}")
+ }
+
+ Timber.tag(loggerTag.value).d("try to direct share Megolm Key for ${validRequest.shortDbgString()}")
+ val sessionHolder = try {
+ olmDevice.getInboundGroupSession(validRequest.sessionId, validRequest.senderKey, validRequest.roomId)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value)
+ .e(failure, "shareKeysWithDevice: failed to get session ${validRequest.requestingUserId}")
+ // It's unavailable
+ sendWithheldForRequest(validRequest, WithHeldCode.UNAVAILABLE)
+ return false
+ }
+
+ val export = sessionHolder.mutex.withLock {
+ sessionHolder.wrapper.exportKeys(0)
+ } ?: return false.also {
+ Timber.tag(loggerTag.value)
+ .e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}")
+ }
+
+ val payloadJson = mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to export
+ )
+
+ val encryptedPayload = messageEncrypter.directEncryptMessage(
+ payloadJson, validRequest.requestingUserId, validRequest.requestingDeviceId, requestingDeviceKey, requestingDeviceOtk)
+ encryptedPayload.traceId = validRequest.sessionId
+
+ val sendToDeviceMap = MXUsersDevicesMap()
+ sendToDeviceMap.setObject(validRequest.requestingUserId, validRequest.requestingDeviceId, encryptedPayload)
+ Timber.tag(loggerTag.value).d(
+ "directShareMegolmKey() : try sending session ${validRequest.sessionId} to ${validRequest.requestingUserId} | ${validRequest.requestingDeviceId}")
+ val sendToDeviceParams = SendToDeviceTask.Params(EventType.ROOM_KEY_REPLY, sendToDeviceMap)
+ return try {
+ sendToDeviceTask.execute(sendToDeviceParams)
+ Timber.tag(loggerTag.value)
+ .i("successfully re-shared session ${validRequest.sessionId} to ${validRequest.requestingUserId} | ${validRequest.requestingDeviceId}")
+ true
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value)
+ .e(failure, "fail to re-share session ${validRequest.sessionId} to ${validRequest.requestingUserId} | ${validRequest.requestingDeviceId}")
+ false
+ }
+ }
+
fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
gossipingRequestListeners.add(listener)
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXCryptoAlgorithms.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXCryptoAlgorithms.kt
index f8ef333..b386097 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXCryptoAlgorithms.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXCryptoAlgorithms.kt
@@ -18,6 +18,7 @@ package org.sdn.android.sdk.internal.crypto
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
// TODO Update comment
internal object MXCryptoAlgorithms {
@@ -30,6 +31,7 @@ internal object MXCryptoAlgorithms {
*/
fun hasEncryptorClassForAlgorithm(algorithm: String?): Boolean {
return when (algorithm) {
+ MXCRYPTO_ALGORITHM_RATCHET,
MXCRYPTO_ALGORITHM_MEGOLM,
MXCRYPTO_ALGORITHM_OLM -> true
else -> false
@@ -45,6 +47,7 @@ internal object MXCryptoAlgorithms {
fun hasDecryptorClassForAlgorithm(algorithm: String?): Boolean {
return when (algorithm) {
+ MXCRYPTO_ALGORITHM_RATCHET,
MXCRYPTO_ALGORITHM_MEGOLM,
MXCRYPTO_ALGORITHM_OLM -> true
else -> false
@@ -55,6 +58,6 @@ internal object MXCryptoAlgorithms {
* @return The list of registered algorithms.
*/
fun supportedAlgorithms(): List {
- return listOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_OLM)
+ return listOf(MXCRYPTO_ALGORITHM_RATCHET, MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_OLM)
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXOlmDevice.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXOlmDevice.kt
index b12fe38..2f97827 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -17,6 +17,7 @@
package org.sdn.android.sdk.internal.crypto
import androidx.annotation.VisibleForTesting
+import com.squareup.moshi.Types
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.sdn.android.sdk.api.extensions.orFalse
@@ -45,6 +46,7 @@ import org.matrix.olm.OlmMessage
import org.matrix.olm.OlmOutboundGroupSession
import org.matrix.olm.OlmSession
import org.matrix.olm.OlmUtility
+import org.sdn.android.sdk.internal.crypto.algorithms.megolm.AESUtil
import timber.log.Timber
import javax.inject.Inject
@@ -155,10 +157,26 @@ internal class MXOlmDevice @Inject constructor(
* Returns an unpublished fallback key.
* A call to markKeysAsPublished will mark it as published and this
* call will return null (until a call to generateFallbackKey is made).
+ * { "curve25519":
+ * {
+ * "AAAABQ":"qefVZd8qvjOpsFzoKSAdfUnJVkIreyxWFlipCHjSQQg"
+ * }
+ * }
*/
fun getFallbackKey(): MutableMap>? {
+ val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
+ val adapterType = Types.newParameterizedType(MutableMap::class.java, String::class.java, mapType)
+ val adapter = MoshiProvider.providesMoshi().adapter>>(adapterType)
try {
- return store.doWithOlmAccount { it.fallbackKey() }
+ val fbk = store.getFbk25519()
+ if(fbk.isNotEmpty()) {
+ return adapter.fromJson(fbk)
+ }
+ val fbkMap = store.doWithOlmAccount { it.fallbackKey() }
+ if (!fbkMap[OlmAccount.JSON_KEY_ONE_TIME_KEY].isNullOrEmpty()) {
+ store.storeFbk25519(adapter.toJson(fbkMap))
+ }
+ return fbkMap
} catch (e: Exception) {
Timber.tag(loggerTag.value).e("## getFallbackKey() : failed")
}
@@ -460,6 +478,41 @@ internal class MXOlmDevice @Inject constructor(
return payloadString
}
+ suspend fun olmEncryptMessage(theirDeviceIdentityKey: String, theirOneTimeKey: String, payloadString: String): Map? {
+ return try {
+ val olmSession = OlmSession()
+ store.doWithOlmAccount { olmAccount ->
+ olmSession.initOutboundSession(olmAccount, theirDeviceIdentityKey, theirOneTimeKey)
+ }
+ val olmMessage = olmSession.encryptMessage(payloadString)
+ mapOf(
+ "body" to olmMessage.mCipherText,
+ "type" to olmMessage.mType,
+ )
+ } catch (e: Throwable) {
+ Timber.tag(loggerTag.value).e(e, "## encryptMessage() : failed to encrypt olm with device:$theirDeviceIdentityKey")
+ null
+ }
+ }
+
+ suspend fun olmDecryptMessage(ciphertext: String, messageType: Int): String? {
+ return try {
+ val olmSession = OlmSession()
+ store.doWithOlmAccount { olmAccount ->
+ olmSession.initInboundSession(olmAccount, ciphertext)
+ }
+
+ val olmMessage = OlmMessage()
+ olmMessage.mCipherText = ciphertext
+ olmMessage.mType = messageType.toLong()
+
+ olmSession.decryptMessage(olmMessage)
+ } catch (e: Exception) {
+ Timber.tag(loggerTag.value).e(e, "## createInboundSession() : the session creation failed")
+ null
+ }
+ }
+
/**
* Determine if an incoming messages is a prekey message matching an existing session.
*
@@ -516,6 +569,7 @@ internal class MXOlmDevice @Inject constructor(
return MXOutboundSessionInfo(
sessionId = sessionId,
+ senderKey = this.deviceCurve25519Key,
sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
clock = clock,
creationTime = restoredOutboundGroupSession.creationTime,
@@ -610,6 +664,7 @@ internal class MXOlmDevice @Inject constructor(
sessionId: String,
sessionKey: String,
roomId: String,
+ algorithm: String,
senderKey: String,
forwardingCurve25519KeyChain: List,
keysClaimed: Map,
@@ -694,6 +749,7 @@ internal class MXOlmDevice @Inject constructor(
val candidateSessionData = InboundGroupSessionData(
senderKey = senderKey,
roomId = roomId,
+ algorithm = algorithm,
keysClaimed = keysClaimed,
forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
sharedHistory = sharedHistory,
@@ -860,6 +916,47 @@ internal class MXOlmDevice @Inject constructor(
)
}
+ @Throws(MXCryptoError::class)
+ suspend fun decryptRatchetMessage(
+ body: String,
+ roomId: String,
+ timeline: String?,
+ eventId: String,
+ sessionId: String,
+ senderKey: String
+ ): OlmDecryptionResult {
+ val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
+ val wrapper = sessionHolder.wrapper
+ val inboundGroupSession = wrapper.session
+ if (roomId != wrapper.roomId) {
+ // Check that the room id matches the original one for the session. This stops
+ // the HS pretending a message was targeting a different room.
+ val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
+ }
+ val internalKey = inboundGroupSession.export(0)
+ Timber.i("aes decrypt with sessionId: $sessionId, key: $internalKey, ciphertext: $body")
+ val decryptedMessage = AESUtil.decrypt(internalKey.toByteArray().copyOfRange(0, 16), body)
+
+ val payload = try {
+ val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE)
+ val payloadString = convertFromUTF8(decryptedMessage)
+ adapter.fromJson(payloadString)
+ } catch (e: Exception) {
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload")
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
+ }
+
+ return OlmDecryptionResult(
+ payload,
+ wrapper.sessionData.keysClaimed,
+ senderKey,
+ wrapper.sessionData.forwardingCurve25519KeyChain,
+ isSafe = sessionHolder.wrapper.sessionData.trusted.orFalse()
+ )
+ }
+
/**
* Reset replay attack data for the given timeline.
*
@@ -944,6 +1041,21 @@ internal class MXOlmDevice @Inject constructor(
}
}
+ /**
+ * Extract the current InboundGroupSession from the session store
+ *
+ * @param roomId the room where the session is used.
+ * @return the inbound group session.
+ */
+ fun getCurrentGroupSession(roomId: String): InboundGroupSessionHolder? {
+
+ val session = store.getCurrentGroupSession(roomId)
+ if (session != null) {
+ return InboundGroupSessionHolder(session)
+ }
+ return null
+ }
+
/**
* Determine if we have the keys for a given megolm session.
*
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MyDeviceInfoHolder.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MyDeviceInfoHolder.kt
index 5839b10..86397a0 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MyDeviceInfoHolder.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MyDeviceInfoHolder.kt
@@ -16,6 +16,7 @@
package org.sdn.android.sdk.internal.crypto
+import org.matrix.olm.OlmAccount
import org.sdn.android.sdk.api.auth.data.Credentials
import org.sdn.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.sdn.android.sdk.api.session.crypto.model.CryptoDeviceInfo
@@ -51,6 +52,15 @@ internal class MyDeviceInfoHolder @Inject constructor(
keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!!
}
+ olmDevice.generateFallbackKeyIfNeeded()
+ olmDevice.getFallbackKey()?.let { fbkMap ->
+ fbkMap[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.let {
+ for (item in it) {
+ keys["fbk25519:" + credentials.deviceId] = item.value
+ }
+ }
+ }
+
// myDevice.keys = keys
//
// myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index 8b1dbda..db76c48 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -94,7 +94,7 @@ internal class OneTimeKeysUploader @Inject constructor(
Timber.w("maybeUploadOneTimeKeys: Failed to get otk count from server")
}
- Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
+ Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync")
lastOneTimeKeyCheck = clock.epochMillis()
@@ -125,15 +125,6 @@ internal class OneTimeKeysUploader @Inject constructor(
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
}
oneTimeKeyCheckInProgress = false
-
- // Check if we need to forget a fallback key
- val latestPublishedTime = getLastFallbackKeyPublishTime()
- if (latestPublishedTime != 0L && clock.epochMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
- // This should be called once you are reasonably certain that you will not receive any more messages
- // that use the old fallback key
- Timber.d("## forgetFallbackKey()")
- olmDevice.forgetFallbackKey()
- }
}
private suspend fun fetchOtkCount(): Int? {
@@ -151,33 +142,19 @@ internal class OneTimeKeysUploader @Inject constructor(
* @return the number of uploaded keys
*/
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int {
- if (keyLimit <= keyCount && !olmDevice.hasUnpublishedFallbackKey()) {
+ if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done.
return 0
}
- var keysThisLoop = 0
- if (keyLimit > keyCount) {
- // Creating keys can be an expensive operation so we limit the
- // number we generate in one go to avoid blocking the application
- // for too long.
- keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
- olmDevice.generateOneTimeKeys(keysThisLoop)
- }
-
- // We check before sending if there is an unpublished key in order to saveLastFallbackKeyPublishTime if needed
- val hadUnpublishedFallbackKey = olmDevice.hasUnpublishedFallbackKey()
+ val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
+ olmDevice.generateOneTimeKeys(keysThisLoop)
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
olmDevice.markKeysAsPublished()
- if (hadUnpublishedFallbackKey) {
- // It had an unpublished fallback key that was published just now
- saveLastFallbackKeyPublishTime(clock.epochMillis())
- }
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
// Maybe upload other keys
return keysThisLoop +
- uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) +
- (if (hadUnpublishedFallbackKey) 1 else 0)
+ uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) + 1
} else {
Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
@@ -213,6 +190,7 @@ internal class OneTimeKeysUploader @Inject constructor(
}
val fallbackJson = mutableMapOf()
+ olmDevice.generateFallbackKeyIfNeeded()
val fallbackCurve25519Map = olmDevice.getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
fallbackCurve25519Map.forEach { (key_id, key) ->
val k = mutableMapOf()
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
index 9de13a8..5f36170 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
@@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.sdn.android.sdk.api.SDNCoroutineDispatchers
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.crypto.MXCryptoConfig
import org.sdn.android.sdk.api.extensions.tryOrNull
import org.sdn.android.sdk.api.logger.LoggerTag
@@ -84,6 +85,12 @@ internal class OutgoingKeyRequestManager @Inject constructor(
private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack>()
fun requestKeyForEvent(event: Event, force: Boolean) {
+
+ if (!force && event.originServerTs != null && event.originServerTs < deviceListManager.getSessionThresholdTime()) {
+ Timber.i("skip requesting keys for old event: ${event.eventId}")
+ return
+ }
+
val (targets, body) = getRoomKeyRequestTargetForEvent(event) ?: return
val index = ratchetIndexForMessage(event) ?: 0
postRoomKeyRequest(body, targets, index, force)
@@ -94,7 +101,7 @@ internal class OutgoingKeyRequestManager @Inject constructor(
val encryptedEventContent = event.content.toModel() ?: return null.also {
Timber.tag(loggerTag.value).e("getRoomKeyRequestTargetForEvent Failed to re-request key, null content")
}
- if (encryptedEventContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
+ if (!arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(encryptedEventContent.algorithm)) return null
val senderDevice = encryptedEventContent.deviceId
val recipients = if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
@@ -285,7 +292,6 @@ internal class OutgoingKeyRequestManager @Inject constructor(
// do we have known requests for that session??
Timber.tag(loggerTag.value).v("Cancel Key Request if needed for $sessionId")
val knownRequest = cryptoStore.getOutgoingRoomKeyRequest(
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
roomId = roomId,
sessionId = sessionId,
senderKey = senderKey
@@ -450,13 +456,26 @@ internal class OutgoingKeyRequestManager @Inject constructor(
}
}
+ deviceListManager.olmDevice.generateFallbackKeyIfNeeded()
+ val fbkMap = deviceListManager.olmDevice.getFallbackKey()
+ val curve25519KeyMap = fbkMap?.get("curve25519")
+ var otk = ""
+ if (curve25519KeyMap != null) {
+ for ((_, value) in curve25519KeyMap) {
+ otk = value
+ }
+ }
+
// we need to send the request
val toDeviceContent = RoomKeyShareRequest(
- requestingDeviceId = cryptoStore.getDeviceId(),
- requestId = request.requestId,
- action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
- body = request.requestBody
+ requestingDeviceId = cryptoStore.getDeviceId(),
+ requestingDeviceKey = deviceListManager.olmDevice.deviceCurve25519Key,
+ requestingDeviceOtk = otk,
+ requestId = request.requestId,
+ action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
+ body = request.requestBody
)
+
val contentMap = MXUsersDevicesMap()
request.recipients.forEach { userToDeviceMap ->
userToDeviceMap.value.forEach { deviceId ->
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomDecryptorProvider.kt
index bf0a3c6..745416e 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomDecryptorProvider.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomDecryptorProvider.kt
@@ -17,8 +17,10 @@
package org.sdn.android.sdk.internal.crypto
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.session.crypto.NewSessionListener
import org.sdn.android.sdk.internal.crypto.algorithms.IMXDecrypting
+import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXRatchetDecryptionFactory
import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryptionFactory
import org.sdn.android.sdk.internal.crypto.algorithms.olm.MXOlmDecryptionFactory
import org.sdn.android.sdk.internal.session.SessionScope
@@ -28,7 +30,8 @@ import javax.inject.Inject
@SessionScope
internal class RoomDecryptorProvider @Inject constructor(
private val olmDecryptionFactory: MXOlmDecryptionFactory,
- private val megolmDecryptionFactory: MXMegolmDecryptionFactory
+ private val megolmDecryptionFactory: MXMegolmDecryptionFactory,
+ private val ratchetDecryptionFactory: MXRatchetDecryptionFactory
) {
// A map from algorithm to MXDecrypting instance, for each room
@@ -85,6 +88,19 @@ internal class RoomDecryptorProvider @Inject constructor(
}
}
}
+ MXCRYPTO_ALGORITHM_RATCHET -> ratchetDecryptionFactory.create().apply {
+ this.newSessionListener = object : NewSessionListener {
+ override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
+ // PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor
+ newSessionListeners.toList().forEach {
+ try {
+ it.onNewSession(roomId, senderKey, sessionId)
+ } catch (ignore: Throwable) {
+ }
+ }
+ }
+ }
+ }
else -> olmDecryptionFactory.create()
}
if (!roomId.isNullOrEmpty()) {
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomEncryptorsStore.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomEncryptorsStore.kt
index f8986fd..d5397db 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomEncryptorsStore.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/RoomEncryptorsStore.kt
@@ -18,8 +18,10 @@ package org.sdn.android.sdk.internal.crypto
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.internal.crypto.algorithms.IMXEncrypting
import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
+import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXRatchetEncryptionFactory
import org.sdn.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
import org.sdn.android.sdk.internal.session.SessionScope
@@ -28,31 +30,33 @@ import javax.inject.Inject
@SessionScope
internal class RoomEncryptorsStore @Inject constructor(
private val cryptoStore: IMXCryptoStore,
+ private val ratchetEncryptionFactory: MXRatchetEncryptionFactory,
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
private val olmEncryptionFactory: MXOlmEncryptionFactory,
) {
// MXEncrypting instance for each room.
- private val roomEncryptors = mutableMapOf()
+ private val roomEncryptors: MutableMap> = HashMap()
- fun put(roomId: String, alg: IMXEncrypting) {
+ fun put(roomId: String, algId: String, alg: IMXEncrypting) {
synchronized(roomEncryptors) {
- roomEncryptors.put(roomId, alg)
+ roomEncryptors.getOrPut(roomId) { mutableMapOf() }.put(algId, alg)
}
}
- fun get(roomId: String): IMXEncrypting? {
+ fun get(roomId: String, algId: String = MXCRYPTO_ALGORITHM_MEGOLM): IMXEncrypting? {
return synchronized(roomEncryptors) {
val cache = roomEncryptors[roomId]
- if (cache != null) {
- return@synchronized cache
+ if (cache != null && cache.containsKey(algId)) {
+ return@synchronized cache[algId]
} else {
- val alg: IMXEncrypting? = when (cryptoStore.getRoomAlgorithm(roomId)) {
+ val alg: IMXEncrypting? = when (algId) {
+ MXCRYPTO_ALGORITHM_RATCHET -> ratchetEncryptionFactory.create(roomId)
MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId)
else -> null
}
- alg?.let { roomEncryptors.put(roomId, it) }
+ alg?.let { roomEncryptors.getOrPut(roomId) { mutableMapOf() }.put(algId, it) }
return@synchronized alg
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
index ebcf01d..a88a484 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
@@ -23,6 +23,7 @@ import org.sdn.android.sdk.api.SDNCoroutineDispatchers
import org.sdn.android.sdk.api.logger.LoggerTag
import org.sdn.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.sdn.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.sdn.android.sdk.api.util.JsonDict
import org.sdn.android.sdk.internal.crypto.MXOlmDevice
import org.sdn.android.sdk.internal.crypto.model.MXKey
import org.sdn.android.sdk.internal.crypto.model.MXOlmSessionResult
@@ -54,29 +55,28 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
val deviceList = devicesByUser.flatMap { it.value }
Timber.tag(loggerTag.value)
.d("ensure olm forced:$force for ${deviceList.joinToString { it.shortDebugString() }}")
+
val devicesToCreateSessionWith = mutableListOf()
- if (force) {
- // we take all devices and will query otk for them
- devicesToCreateSessionWith.addAll(deviceList)
- } else {
- // only peek devices without active session
- deviceList.forEach { deviceInfo ->
- val deviceId = deviceInfo.deviceId
- val userId = deviceInfo.userId
- val key = deviceInfo.identityKey() ?: return@forEach Unit.also {
- Timber.tag(loggerTag.value).w("Ignoring device ${deviceInfo.shortDebugString()} without identity key")
- }
+ val devicesToClaimKeyWith = mutableListOf()
+ deviceList.forEach { deviceInfo ->
+ val deviceId = deviceInfo.deviceId
+ val userId = deviceInfo.userId
+ val key = deviceInfo.identityKey() ?: return@forEach Unit.also {
+ Timber.tag(loggerTag.value).w("Ignoring device ${deviceInfo.shortDebugString()} without identity key")
+ }
- // is there a session that as been already used?
- val sessionId = olmDevice.getSessionId(key)
- if (sessionId.isNullOrEmpty()) {
- Timber.tag(loggerTag.value).d("Found no existing olm session ${deviceInfo.shortDebugString()} add to claim list")
- devicesToCreateSessionWith.add(deviceInfo)
- } else {
- Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
- val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
- results.setObject(userId, deviceId, olmSessionResult)
+ // is there a session that as been already used?
+ val sessionId = olmDevice.getSessionId(key)
+ if (force || sessionId.isNullOrEmpty()) {
+ Timber.tag(loggerTag.value).d("Found no existing olm session ${deviceInfo.shortDebugString()} add to claim list")
+ if (deviceInfo.fallbackKey().isNullOrEmpty()) {
+ devicesToClaimKeyWith.add(deviceInfo)
}
+ devicesToCreateSessionWith.add(deviceInfo)
+ } else {
+ Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
+ val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
+ results.setObject(userId, deviceId, olmSessionResult)
}
}
@@ -84,24 +84,37 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
// no session to create
return results
}
- val usersDevicesToClaim = MXUsersDevicesMap().apply {
- devicesToCreateSessionWith.forEach {
- setObject(it.userId, it.deviceId, MXKey.KEY_SIGNED_CURVE_25519_TYPE)
- }
- }
// Let's now claim one time keys
- val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
- val oneTimeKeys = withContext(coroutineDispatchers.io) {
- oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
+ var oneTimeKeys = MXUsersDevicesMap()
+ if (devicesToClaimKeyWith.isNotEmpty()) {
+ val usersDevicesToClaim = MXUsersDevicesMap().apply {
+ devicesToClaimKeyWith.forEach {
+ setObject(it.userId, it.deviceId, MXKey.KEY_SIGNED_CURVE_25519_TYPE)
+ }
+ }
+ val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
+ oneTimeKeys = withContext(coroutineDispatchers.io) {
+ oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
+ }
}
// let now start olm session using the new otks
devicesToCreateSessionWith.forEach { deviceInfo ->
val userId = deviceInfo.userId
val deviceId = deviceInfo.deviceId
+ val fbk = deviceInfo.fallbackKey()
// Did we get an OTK
- val oneTimeKey = oneTimeKeys.getObject(userId, deviceId)
+ var oneTimeKey = oneTimeKeys.getObject(userId, deviceId)
+ if (oneTimeKey == null && !fbk.isNullOrEmpty()) {
+ oneTimeKey = MXKey(
+ type = MXKey.KEY_SIGNED_CURVE_25519_TYPE,
+ keyId = "",
+ value = fbk,
+ signatures = emptyMap(),
+ rawMap = emptyMap()
+ )
+ }
if (oneTimeKey == null) {
Timber.tag(loggerTag.value).d("No otk for ${deviceInfo.shortDebugString()}")
} else if (oneTimeKey.type != MXKey.KEY_SIGNED_CURVE_25519_TYPE) {
@@ -124,19 +137,17 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? {
var sessionId: String? = null
+ var isVerified = true
+ var errorMessage: String? = null
val deviceId = deviceInfo.deviceId
val signKeyId = "ed25519:$deviceId"
val signature = oneTimeKey.signatureForUserId(userId, signKeyId)
-
val fingerprint = deviceInfo.fingerprint()
- if (!signature.isNullOrEmpty() && !fingerprint.isNullOrEmpty()) {
- var isVerified = false
- var errorMessage: String? = null
+ if (!signature.isNullOrEmpty() && !fingerprint.isNullOrEmpty()) {
try {
olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature)
- isVerified = true
} catch (e: Exception) {
Timber.tag(loggerTag.value).d(
e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
@@ -146,24 +157,24 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
"verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
" - signable json ${oneTimeKey.signalableJSONDictionary()}"
)
+ isVerified = false
errorMessage = e.message
}
+ }
+ // Check one-time key signature
+ if (isVerified) {
+ sessionId = deviceInfo.identityKey()?.let { identityKey ->
+ olmDevice.createOutboundSession(identityKey, oneTimeKey.value)
+ }
- // Check one-time key signature
- if (isVerified) {
- sessionId = deviceInfo.identityKey()?.let { identityKey ->
- olmDevice.createOutboundSession(identityKey, oneTimeKey.value)
- }
-
- if (sessionId.isNullOrEmpty()) {
- // Possibly a bad key
- Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
- } else {
- Timber.tag(loggerTag.value).d("verifyKeyAndStartSession() : Started new sessionId $sessionId for device $userId:$deviceId")
- }
+ if (sessionId.isNullOrEmpty()) {
+ // Possibly a bad key
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
} else {
- Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Unable to verify otk signature for $userId:$deviceId: $errorMessage")
+ Timber.tag(loggerTag.value).d("verifyKeyAndStartSession() : Started new sessionId $sessionId for device $userId:$deviceId")
}
+ } else {
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Unable to verify otk signature for $userId:$deviceId: $errorMessage")
}
return sessionId
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
index 161056f..53ff3da 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
@@ -26,6 +26,7 @@ import org.sdn.android.sdk.internal.crypto.MegolmSessionData
import org.sdn.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.sdn.android.sdk.internal.crypto.RoomDecryptorProvider
import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption
+import org.sdn.android.sdk.internal.crypto.algorithms.megolm.MXRatchetDecryption
import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
import org.sdn.android.sdk.internal.util.time.Clock
import timber.log.Timber
@@ -95,6 +96,9 @@ internal class MegolmSessionDataImporter @Inject constructor(
is MXMegolmDecryption -> {
decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!)
}
+ is MXRatchetDecryption -> {
+ decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!)
+ }
}
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## importRoomKeys() : onNewSession failed")
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MessageEncrypter.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MessageEncrypter.kt
index 2207bac..80c5202 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MessageEncrypter.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/actions/MessageEncrypter.kt
@@ -86,4 +86,24 @@ internal class MessageEncrypter @Inject constructor(
cipherText = ciphertext
)
}
+
+ suspend fun directEncryptMessage(payloadFields: Content, userId: String, deviceId: String,
+ deviceCurve25519Key: String, deviceOtk: String): EncryptedMessage {
+
+ val payloadJson = payloadFields.toMutableMap()
+ payloadJson["sender"] = this.userId
+ payloadJson["sender_device"] = this.deviceId!!
+ payloadJson["keys"] = mapOf("ed25519" to olmDevice.deviceEd25519Key!!)
+ payloadJson["recipient"] = userId
+ val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
+
+ val ciphertext = mutableMapOf()
+ ciphertext[deviceCurve25519Key] = olmDevice.olmEncryptMessage(deviceCurve25519Key, deviceOtk, payloadString)!!
+
+ return EncryptedMessage(
+ algorithm = MXCRYPTO_ALGORITHM_OLM,
+ senderKey = olmDevice.deviceCurve25519Key,
+ cipherText = ciphertext
+ )
+ }
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/AESUtil.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/AESUtil.kt
new file mode 100644
index 0000000..acf2880
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/AESUtil.kt
@@ -0,0 +1,41 @@
+package org.sdn.android.sdk.internal.crypto.algorithms.megolm
+
+import android.text.TextUtils
+import android.util.Base64
+import timber.log.Timber
+import javax.crypto.Cipher
+import javax.crypto.spec.IvParameterSpec
+import javax.crypto.spec.SecretKeySpec
+
+class AESUtil {
+ companion object {
+ private const val algorithm = "AES"
+ private const val cipherMode = "AES/CBC/PKCS7Padding" //algorithm/mode/padding
+
+ fun encrypt(key: ByteArray, cleartext: String): String {
+ if (TextUtils.isEmpty(cleartext)) {
+ return cleartext
+ }
+ val cipher: Cipher = Cipher.getInstance(cipherMode)
+ val keySpec = SecretKeySpec(key, algorithm)
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, IvParameterSpec(key.copyOfRange(0, cipher.blockSize)))
+ val encryptedBytes = cipher.doFinal(cleartext.toByteArray())
+
+ return Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
+ }
+
+ fun decrypt(key: ByteArray, ciphertext: String): String {
+ if (TextUtils.isEmpty(ciphertext)) {
+ return ciphertext
+ }
+ val encryptedBytes = Base64.decode(ciphertext, Base64.DEFAULT)
+ val cipher: Cipher = Cipher.getInstance(cipherMode)
+ val keySpec = SecretKeySpec(key, algorithm)
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(key.copyOfRange(0, cipher.blockSize)))
+ val decryptedBytes = cipher.doFinal(encryptedBytes)
+
+ return decryptedBytes.decodeToString()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index 1df7fe4..64f1221 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -17,6 +17,7 @@
package org.sdn.android.sdk.internal.crypto.algorithms.megolm
import dagger.Lazy
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.api.crypto.MXCryptoConfig
import org.sdn.android.sdk.api.extensions.orFalse
import org.sdn.android.sdk.api.logger.LoggerTag
@@ -244,11 +245,10 @@ internal class MXMegolmDecryption(
val wasNotRequested = cryptoStore.getOutgoingRoomKeyRequest(
roomId = forwardedRoomKeyContent.roomId.orEmpty(),
sessionId = forwardedRoomKeyContent.sessionId.orEmpty(),
- algorithm = forwardedRoomKeyContent.algorithm.orEmpty(),
senderKey = forwardedRoomKeyContent.senderKey.orEmpty(),
).isEmpty()
- trusted = false
+ trusted = forceAccept
if (!forceAccept && wasNotRequested) {
// val senderId = cryptoStore.deviceWithIdentityKey(event.getSenderKey().orEmpty())?.userId.orEmpty()
@@ -258,26 +258,25 @@ internal class MXMegolmDecryption(
return
}
- // Check who sent the request, as we requested we have the device keys (no need to download)
- val sessionThatIsSharing = cryptoStore.deviceWithIdentityKey(eventSenderKey)
- if (sessionThatIsSharing == null) {
- Timber.tag(loggerTag.value).w("Ignoring forwarded_room_key from unknown device with identity $eventSenderKey")
- return
- }
- val isOwnDevice = myUserId == sessionThatIsSharing.userId
- val isDeviceVerified = sessionThatIsSharing.isVerified
- val isFromSessionInitiator = sessionThatIsSharing.identityKey() == sessionInitiatorSenderKey
-
- val isLegitForward = (isOwnDevice && isDeviceVerified) ||
- (!cryptoConfig.limitRoomKeyRequestsToMyDevices && isFromSessionInitiator)
-
- val shouldAcceptForward = forceAccept || isLegitForward
-
- if (!shouldAcceptForward) {
- Timber.tag(loggerTag.value)
+ if (!forceAccept) {
+ // Check who sent the request, as we requested we have the device keys (no need to download)
+ val sessionThatIsSharing = cryptoStore.deviceWithIdentityKey(eventSenderKey)
+ if (sessionThatIsSharing == null) {
+ Timber.tag(loggerTag.value).w("Ignoring forwarded_room_key from unknown device with identity $eventSenderKey")
+ return
+ }
+ val isOwnDevice = myUserId == sessionThatIsSharing.userId
+ val isDeviceVerified = sessionThatIsSharing.isVerified
+ val isFromSessionInitiator = sessionThatIsSharing.identityKey() == sessionInitiatorSenderKey
+
+ val isLegitForward = (isOwnDevice && isDeviceVerified) ||
+ (!cryptoConfig.limitRoomKeyRequestsToMyDevices && isFromSessionInitiator)
+ if (!isLegitForward) {
+ Timber.tag(loggerTag.value)
.w("Ignoring forwarded_room_key device:$eventSenderKey, ownVerified:{$isOwnDevice&&$isDeviceVerified}," +
" fromInitiator:$isFromSessionInitiator")
- return
+ return
+ }
}
} else {
// It's a m.room_key so safe
@@ -293,6 +292,7 @@ internal class MXMegolmDecryption(
sessionId = roomKeyContent.sessionId,
sessionKey = roomKeyContent.sessionKey,
roomId = roomKeyContent.roomId,
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
senderKey = sessionInitiatorSenderKey,
forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
keysClaimed = keysClaimed,
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index 4caeb74..3b6348a 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -176,6 +176,7 @@ internal class MXMegolmEncryption(
sessionId = sessionId!!,
sessionKey = olmDevice.getSessionKey(sessionId)!!,
roomId = roomId,
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
senderKey = olmDevice.deviceCurve25519Key!!,
forwardingCurve25519KeyChain = emptyList(),
keysClaimed = keysClaimedMap,
@@ -188,6 +189,7 @@ internal class MXMegolmEncryption(
return MXOutboundSessionInfo(
sessionId = sessionId,
+ senderKey = olmDevice.deviceCurve25519Key,
sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore),
clock = clock,
sharedHistory = sharedHistory
@@ -202,13 +204,7 @@ internal class MXMegolmEncryption(
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap): MXOutboundSessionInfo {
Timber.tag(loggerTag.value).v("ensureOutboundSession roomId:$roomId")
var session = outboundSession
- if (session == null ||
- // Need to make a brand new session?
- session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) ||
- // Is there a room history visibility change since the last outboundSession
- cryptoStore.shouldShareHistory(roomId) != session.sharedHistory ||
- // Determine if we have shared with anyone we shouldn't have
- session.sharedWithTooManyDevices(devicesInRoom)) {
+ if (session == null) {
Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.")
session = prepareNewSessionInRoom()
outboundSession = session
@@ -318,7 +314,9 @@ internal class MXMegolmEncryption(
continue
}
Timber.tag(loggerTag.value).v("shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
- contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
+ val encryptedMessage = messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo))
+ encryptedMessage.traceId = sessionInfo.sessionId
+ contentMap.setObject(userId, deviceID, encryptedMessage)
haveTargets = true
}
}
@@ -540,6 +538,7 @@ internal class MXMegolmEncryption(
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
+ encodedPayload.traceId = groupSessionId
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.tag(loggerTag.value).i("reshareKey() : sending session $groupSessionId to $userId:$deviceId")
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
index f1e54f6..ab81bbb 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
@@ -23,12 +23,15 @@ import timber.log.Timber
internal class MXOutboundSessionInfo(
// The id of the session
- val sessionId: String,
- val sharedWithHelper: SharedWithHelper,
- private val clock: Clock,
+ val sessionId: String,
+ val senderKey: String?,
+ val sharedWithHelper: SharedWithHelper,
+ private val clock: Clock,
// When the session was created
- private val creationTime: Long = clock.epochMillis(),
- val sharedHistory: Boolean = false
+ private val creationTime: Long = clock.epochMillis(),
+ val sharedHistory: Boolean = false,
+ val updatedUserDevices: HashMap> = HashMap(),
+ var sharedSessionMap: Map> = HashMap()
) {
// Number of times this session has been used
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetDecryption.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetDecryption.kt
new file mode 100644
index 0000000..44c894a
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetDecryption.kt
@@ -0,0 +1,366 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.algorithms.megolm
+
+import dagger.Lazy
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
+import org.sdn.android.sdk.api.crypto.MXCryptoConfig
+import org.sdn.android.sdk.api.extensions.orFalse
+import org.sdn.android.sdk.api.logger.LoggerTag
+import org.sdn.android.sdk.api.session.crypto.MXCryptoError
+import org.sdn.android.sdk.api.session.crypto.NewSessionListener
+import org.sdn.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent
+import org.sdn.android.sdk.api.session.crypto.model.MXEventDecryptionResult
+import org.sdn.android.sdk.api.session.events.model.Event
+import org.sdn.android.sdk.api.session.events.model.EventType
+import org.sdn.android.sdk.api.session.events.model.content.EncryptedEventContent
+import org.sdn.android.sdk.api.session.events.model.content.RoomKeyContent
+import org.sdn.android.sdk.api.session.events.model.toModel
+import org.sdn.android.sdk.internal.crypto.MXOlmDevice
+import org.sdn.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.sdn.android.sdk.internal.crypto.algorithms.IMXDecrypting
+import org.sdn.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
+import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.sdn.android.sdk.internal.session.StreamEventsManager
+import org.sdn.android.sdk.internal.util.time.Clock
+import timber.log.Timber
+
+private val loggerTag = LoggerTag("MXRatchetDecryption", LoggerTag.CRYPTO)
+
+internal class MXRatchetDecryption(
+ private val olmDevice: MXOlmDevice,
+ private val myUserId: String,
+ private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+ private val cryptoStore: IMXCryptoStore,
+ private val liveEventManager: Lazy,
+ private val unrequestedForwardManager: UnRequestedForwardManager,
+ private val cryptoConfig: MXCryptoConfig,
+ private val clock: Clock,
+) : IMXDecrypting {
+
+ var newSessionListener: NewSessionListener? = null
+
+ /**
+ * Events which we couldn't decrypt due to unknown sessions / indexes: map from
+ * senderKey|sessionId to timelines to list of MatrixEvents.
+ */
+// private var pendingEvents: MutableMap>> = HashMap()
+
+ @Throws(MXCryptoError::class)
+ override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ return decryptEvent(event, timeline, true)
+ }
+
+ @Throws(MXCryptoError::class)
+ private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
+ Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
+ if (event.roomId.isNullOrBlank()) {
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
+ }
+
+ val encryptedEventContent = event.content.toModel()
+ ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
+
+ if (encryptedEventContent.senderKey.isNullOrBlank() ||
+ encryptedEventContent.sessionId.isNullOrBlank() ||
+ encryptedEventContent.ciphertext.isNullOrBlank()) {
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
+ }
+
+ return runCatching {
+ olmDevice.decryptRatchetMessage(
+ encryptedEventContent.ciphertext,
+ event.roomId,
+ timeline,
+ eventId = event.eventId.orEmpty(),
+ encryptedEventContent.sessionId,
+ encryptedEventContent.senderKey
+ )
+ }
+ .fold(
+ { olmDecryptionResult ->
+ // the decryption succeeds
+ if (olmDecryptionResult.payload != null) {
+ MXEventDecryptionResult(
+ clearEvent = olmDecryptionResult.payload,
+ senderCurve25519Key = olmDecryptionResult.senderKey,
+ claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
+ forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
+ .orEmpty(),
+ isSafe = olmDecryptionResult.isSafe.orFalse()
+ ).also {
+ liveEventManager.get().dispatchLiveEventDecrypted(event, it)
+ }
+ } else {
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
+ }
+ },
+ { throwable ->
+ liveEventManager.get().dispatchLiveEventDecryptionFailed(event, throwable)
+ if (throwable is MXCryptoError.OlmError) {
+ // TODO Check the value of .message
+ if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
+ // So we know that session, but it's ratcheted and we can't decrypt at that index
+
+ if (requestKeysOnFail) {
+ requestKeysForEvent(event)
+ }
+ // Check if partially withheld
+ val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
+ if (withHeldInfo != null) {
+ // Encapsulate as withHeld exception
+ throw MXCryptoError.Base(
+ MXCryptoError.ErrorType.KEYS_WITHHELD,
+ withHeldInfo.code?.value ?: "",
+ withHeldInfo.reason
+ )
+ }
+
+ throw MXCryptoError.Base(
+ MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
+ "UNKNOWN_MESSAGE_INDEX",
+ null
+ )
+ }
+
+ val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
+ val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
+
+ throw MXCryptoError.Base(
+ MXCryptoError.ErrorType.OLM,
+ reason,
+ detailedReason
+ )
+ }
+ if (throwable is MXCryptoError.Base) {
+ if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
+ // Check if it was withheld by sender to enrich error code
+ val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
+ if (withHeldInfo != null) {
+ if (requestKeysOnFail) {
+ requestKeysForEvent(event)
+ }
+ // Encapsulate as withHeld exception
+ throw MXCryptoError.Base(
+ MXCryptoError.ErrorType.KEYS_WITHHELD,
+ withHeldInfo.code?.value ?: "",
+ withHeldInfo.reason
+ )
+ }
+
+ if (requestKeysOnFail) {
+ requestKeysForEvent(event)
+ }
+ }
+ }
+ throw throwable
+ }
+ )
+ }
+
+ /**
+ * Helper for the real decryptEvent and for _retryDecryption. If
+ * requestKeysOnFail is true, we'll send an m.room_key_request when we fail
+ * to decrypt the event due to missing megolm keys.
+ *
+ * @param event the event
+ */
+ private fun requestKeysForEvent(event: Event) {
+ outgoingKeyRequestManager.requestKeyForEvent(event, false)
+ }
+
+ /**
+ * Handle a key event.
+ *
+ * @param event the key event.
+ * @param defaultKeysBackupService the keys backup service
+ * @param forceAccept if true will force to accept the forwarded key
+ */
+ override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) {
+ Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})")
+ var exportFormat = false
+ val roomKeyContent = event.getDecryptedContent()?.toModel() ?: return
+
+ val eventSenderKey: String = event.getSenderKey() ?: return Unit.also {
+ Timber.tag(loggerTag.value).e("onRoom Key/Forward Event() : event is missing sender_key field")
+ }
+
+ // this device might not been downloaded now?
+ val fromDevice = cryptoStore.deviceWithIdentityKey(eventSenderKey)
+
+ lateinit var sessionInitiatorSenderKey: String
+ val trusted: Boolean
+
+ var keysClaimed: MutableMap = HashMap()
+ val forwardingCurve25519KeyChain: MutableList = ArrayList()
+
+ if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.sessionId.isNullOrEmpty() || roomKeyContent.sessionKey.isNullOrEmpty()) {
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : Key event is missing fields")
+ return
+ }
+ if (event.getDecryptedType() == EventType.FORWARDED_ROOM_KEY) {
+ if (!cryptoStore.isKeyGossipingEnabled()) {
+ Timber.tag(loggerTag.value)
+ .i("onRoomKeyEvent(), ignore forward adding as per crypto config : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ return
+ }
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ val forwardedRoomKeyContent = event.getDecryptedContent()?.toModel()
+ ?: return
+
+ forwardedRoomKeyContent.forwardingCurve25519KeyChain?.let {
+ forwardingCurve25519KeyChain.addAll(it)
+ }
+
+ forwardingCurve25519KeyChain.add(eventSenderKey)
+
+ exportFormat = true
+ sessionInitiatorSenderKey = forwardedRoomKeyContent.senderKey ?: return Unit.also {
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
+ }
+
+ if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) {
+ Timber.tag(loggerTag.value).e("forwarded_room_key_event is missing sender_claimed_ed25519_key field")
+ return
+ }
+
+ keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
+
+ // checking if was requested once.
+ // should we check if the request is sort of active?
+ val wasNotRequested = cryptoStore.getOutgoingRoomKeyRequest(
+ roomId = forwardedRoomKeyContent.roomId.orEmpty(),
+ sessionId = forwardedRoomKeyContent.sessionId.orEmpty(),
+ senderKey = forwardedRoomKeyContent.senderKey.orEmpty(),
+ ).isEmpty()
+
+ trusted = false
+
+ if (!forceAccept && wasNotRequested) {
+// val senderId = cryptoStore.deviceWithIdentityKey(event.getSenderKey().orEmpty())?.userId.orEmpty()
+ unrequestedForwardManager.onUnRequestedKeyForward(roomKeyContent.roomId, event, clock.epochMillis())
+ // Ignore unsolicited
+ Timber.tag(loggerTag.value).w("Ignoring forwarded_room_key_event for ${roomKeyContent.sessionId} that was not requested")
+ return
+ }
+
+ // Check who sent the request, as we requested we have the device keys (no need to download)
+ val sessionThatIsSharing = cryptoStore.deviceWithIdentityKey(eventSenderKey)
+ if (sessionThatIsSharing == null) {
+ Timber.tag(loggerTag.value).w("Received forwarded_room_key from unknown device with identity $eventSenderKey")
+ // keep the key anyway
+ }
+ val isOwnDevice = myUserId == sessionThatIsSharing?.userId
+ val isDeviceVerified = sessionThatIsSharing?.isVerified ?: false
+ val isFromSessionInitiator = sessionThatIsSharing?.identityKey() == sessionInitiatorSenderKey
+
+ val isLegitForward = (isOwnDevice && isDeviceVerified) ||
+ (!cryptoConfig.limitRoomKeyRequestsToMyDevices && isFromSessionInitiator)
+
+ val shouldAcceptForward = forceAccept || isLegitForward
+
+ if (!shouldAcceptForward) {
+ Timber.tag(loggerTag.value)
+ .w("Ignoring forwarded_room_key device:$eventSenderKey, ownVerified:{$isOwnDevice&&$isDeviceVerified}," +
+ " fromInitiator:$isFromSessionInitiator")
+ return
+ }
+ } else {
+ // It's a m.room_key so safe
+ trusted = true
+ sessionInitiatorSenderKey = eventSenderKey
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ // inherit the claimed ed25519 key from the setup message
+ keysClaimed = event.getKeysClaimed().toMutableMap()
+ }
+
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
+ val addSessionResult = olmDevice.addInboundGroupSession(
+ sessionId = roomKeyContent.sessionId,
+ sessionKey = roomKeyContent.sessionKey,
+ roomId = roomKeyContent.roomId,
+ algorithm = MXCRYPTO_ALGORITHM_RATCHET,
+ senderKey = sessionInitiatorSenderKey,
+ forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
+ keysClaimed = keysClaimed,
+ exportFormat = exportFormat,
+ sharedHistory = roomKeyContent.getSharedKey(),
+ trusted = trusted
+ ).also {
+ Timber.tag(loggerTag.value).v(".. onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId} result: $it")
+ }
+
+ when (addSessionResult) {
+ is MXOlmDevice.AddSessionResult.Imported -> addSessionResult.ratchetIndex
+ is MXOlmDevice.AddSessionResult.NotImportedHigherIndex -> addSessionResult.newIndex
+ else -> null
+ }?.let { index ->
+ if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
+ outgoingKeyRequestManager.onRoomKeyForwarded(
+ sessionId = roomKeyContent.sessionId,
+ algorithm = roomKeyContent.algorithm ?: "",
+ roomId = roomKeyContent.roomId,
+ senderKey = sessionInitiatorSenderKey,
+ fromIndex = index,
+ fromDevice = fromDevice?.deviceId,
+ event = event
+ )
+
+ cryptoStore.saveIncomingForwardKeyAuditTrail(
+ roomId = roomKeyContent.roomId,
+ sessionId = roomKeyContent.sessionId,
+ senderKey = sessionInitiatorSenderKey,
+ algorithm = roomKeyContent.algorithm ?: "",
+ userId = event.senderId.orEmpty(),
+ deviceId = fromDevice?.deviceId.orEmpty(),
+ chainIndex = index.toLong()
+ )
+
+ // The index is used to decide if we cancel sent request or if we wait for a better key
+ outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, sessionInitiatorSenderKey, index)
+ }
+ }
+
+ if (addSessionResult is MXOlmDevice.AddSessionResult.Imported) {
+ Timber.tag(loggerTag.value)
+ .d("onRoomKeyEvent(${event.getClearType()}) : Added megolm session ${roomKeyContent.sessionId} in ${roomKeyContent.roomId}")
+ defaultKeysBackupService.maybeBackupKeys()
+
+ onNewSession(roomKeyContent.roomId, sessionInitiatorSenderKey, roomKeyContent.sessionId)
+ }
+ }
+
+ /**
+ * Returns boolean shared key flag.
+ */
+ private fun RoomKeyContent.getSharedKey(): Boolean {
+ if (!cryptoStore.isShareKeysOnInviteEnabled()) return false
+ return sharedHistory ?: false
+ }
+
+ /**
+ * Check if the some messages can be decrypted with a new session.
+ *
+ * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions
+ * @param senderKey the session sender key
+ * @param sessionId the session id
+ */
+ fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
+ Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
+ newSessionListener?.onNewSession(roomId, senderKey, sessionId)
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetDecryptionFactory.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetDecryptionFactory.kt
new file mode 100644
index 0000000..77c1269
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetDecryptionFactory.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.algorithms.megolm
+
+import dagger.Lazy
+import org.sdn.android.sdk.api.crypto.MXCryptoConfig
+import org.sdn.android.sdk.internal.crypto.MXOlmDevice
+import org.sdn.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.sdn.android.sdk.internal.di.UserId
+import org.sdn.android.sdk.internal.session.StreamEventsManager
+import org.sdn.android.sdk.internal.util.time.Clock
+import javax.inject.Inject
+
+internal class MXRatchetDecryptionFactory @Inject constructor(
+ private val olmDevice: MXOlmDevice,
+ @UserId private val myUserId: String,
+ private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+ private val cryptoStore: IMXCryptoStore,
+ private val eventsManager: Lazy,
+ private val unrequestedForwardManager: UnRequestedForwardManager,
+ private val mxCryptoConfig: MXCryptoConfig,
+ private val clock: Clock,
+) {
+
+ fun create(): MXRatchetDecryption {
+ return MXRatchetDecryption(
+ olmDevice = olmDevice,
+ myUserId = myUserId,
+ outgoingKeyRequestManager = outgoingKeyRequestManager,
+ cryptoStore = cryptoStore,
+ liveEventManager = eventsManager,
+ unrequestedForwardManager = unrequestedForwardManager,
+ cryptoConfig = mxCryptoConfig,
+ clock = clock,
+ )
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetEncryption.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetEncryption.kt
new file mode 100644
index 0000000..f41fb73
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetEncryption.kt
@@ -0,0 +1,693 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.algorithms.megolm
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import org.sdn.android.sdk.api.SDNCoroutineDispatchers
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
+import org.sdn.android.sdk.api.extensions.tryOrNull
+import org.sdn.android.sdk.api.logger.LoggerTag
+import org.sdn.android.sdk.api.session.crypto.MXCryptoError
+import org.sdn.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.sdn.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.sdn.android.sdk.api.session.crypto.model.forEach
+import org.sdn.android.sdk.api.session.events.model.Content
+import org.sdn.android.sdk.api.session.events.model.EventType
+import org.sdn.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.sdn.android.sdk.api.session.events.model.content.WithHeldCode
+import org.sdn.android.sdk.api.session.room.model.message.MessageContent
+import org.sdn.android.sdk.api.session.room.model.message.MessageType
+import org.sdn.android.sdk.internal.crypto.DeviceListManager
+import org.sdn.android.sdk.internal.crypto.InboundGroupSessionHolder
+import org.sdn.android.sdk.internal.crypto.MXOlmDevice
+import org.sdn.android.sdk.internal.crypto.MegolmSessionData
+import org.sdn.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
+import org.sdn.android.sdk.internal.crypto.actions.MessageEncrypter
+import org.sdn.android.sdk.internal.crypto.algorithms.IMXEncrypting
+import org.sdn.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
+import org.sdn.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
+import org.sdn.android.sdk.internal.crypto.model.toDebugCount
+import org.sdn.android.sdk.internal.crypto.model.toDebugString
+import org.sdn.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
+import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.sdn.android.sdk.internal.crypto.tasks.GetSessionMapTask
+import org.sdn.android.sdk.internal.crypto.tasks.PutSessionMapTask
+import org.sdn.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.sdn.android.sdk.internal.util.JsonCanonicalizer
+import org.sdn.android.sdk.internal.util.convertToUTF8
+import org.sdn.android.sdk.internal.util.time.Clock
+import timber.log.Timber
+import java.lang.Exception
+
+private val loggerTag = LoggerTag("MXRatchetEncryption", LoggerTag.CRYPTO)
+
+internal class MXRatchetEncryption(
+ // The id of the room we will be sending to.
+ private val roomId: String,
+ private val olmDevice: MXOlmDevice,
+ private val defaultKeysBackupService: DefaultKeysBackupService,
+ private val cryptoStore: IMXCryptoStore,
+ private val deviceListManager: DeviceListManager,
+ private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
+ private val myUserId: String,
+ private val myDeviceId: String,
+ private val sendToDeviceTask: SendToDeviceTask,
+ private val getSessionMapTask: GetSessionMapTask,
+ private val putSessionMapTask: PutSessionMapTask,
+ private val messageEncrypter: MessageEncrypter,
+ private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
+ private val coroutineDispatchers: SDNCoroutineDispatchers,
+ private val cryptoCoroutineScope: CoroutineScope,
+ private val clock: Clock,
+) : IMXEncrypting, IMXGroupEncryption {
+
+ // OutboundSessionInfo. Null if we haven't yet started setting one up. Note
+ // that even if this is non-null, it may not be ready for use (in which
+ // case outboundSession.shareOperation will be non-null.)
+ private var outboundSession: MXOutboundSessionInfo? = null
+
+ init {
+ // restore existing outbound session if any
+ outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId)
+ }
+
+ override suspend fun encryptEventContent(
+ eventContent: Content,
+ eventType: String,
+ userIds: List
+ ): Content {
+ val ts = clock.epochMillis()
+ Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
+
+ /**
+ * When using in-room messages and the room has encryption enabled,
+ * clients should ensure that encryption does not hinder the verification.
+ * For example, if the verification messages are encrypted, clients must ensure that all the recipient’s
+ * unverified devices receive the keys necessary to decrypt the messages,
+ * even if they would normally not be given the keys to decrypt messages in the room.
+ */
+ val shouldSendToUnverified = isVerificationEvent(eventType, eventContent)
+
+ val devices = getDevicesInRoom(userIds, forceDistributeToUnverified = shouldSendToUnverified)
+
+ Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
+ Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
+ val outboundSession = ensureCurrentSession(devices.allowedDevices)
+
+ return encryptContent(outboundSession, eventType, eventContent)
+ .also {
+ notifyWithheldForSession(devices.withHeldDevices, outboundSession)
+ // annoyingly we have to serialize again the saved outbound session to store message index :/
+ // if not we would see duplicate message index errors
+ olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
+ Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${clock.epochMillis() - ts} millis")
+ }
+ }
+
+ private fun isVerificationEvent(eventType: String, eventContent: Content) =
+ EventType.isVerificationEvent(eventType) ||
+ (eventType == EventType.MESSAGE &&
+ eventContent.get(MessageContent.MSG_TYPE_JSON_KEY) == MessageType.MSGTYPE_VERIFICATION_REQUEST)
+
+ private fun notifyWithheldForSession(devices: MXUsersDevicesMap, outboundSession: MXOutboundSessionInfo) {
+ // offload to computation thread
+ cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
+ mutableListOf>().apply {
+ devices.forEach { userId, deviceId, withheldCode ->
+ this.add(UserDevice(userId, deviceId) to withheldCode)
+ }
+ }.groupBy(
+ { it.second },
+ { it.first }
+ ).forEach { (code, targets) ->
+ notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code)
+ }
+ }
+ }
+
+ override fun discardSessionKey() {
+ outboundSession = null
+ olmDevice.discardOutboundGroupSessionForRoom(roomId)
+ }
+
+ override suspend fun preshareKey(userIds: List) {
+ val ts = clock.epochMillis()
+ Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...")
+ val devices = getDevicesInRoom(userIds)
+ val outboundSession = ensureCurrentSession(devices.allowedDevices)
+
+ notifyWithheldForSession(devices.withHeldDevices, outboundSession)
+
+ Timber.tag(loggerTag.value).d("preshareKey in $roomId done in ${clock.epochMillis() - ts} millis")
+ }
+
+ /**
+ * Prepare a new session.
+ *
+ * @return the session description
+ */
+ private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
+ Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() ")
+ val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
+
+ val keysClaimedMap = mapOf(
+ "ed25519" to olmDevice.deviceEd25519Key!!
+ )
+
+ val sharedHistory = cryptoStore.shouldShareHistory(roomId)
+ Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() as sharedHistory $sharedHistory")
+ olmDevice.addInboundGroupSession(
+ sessionId = sessionId!!,
+ sessionKey = olmDevice.getSessionKey(sessionId)!!,
+ roomId = roomId,
+ algorithm = MXCRYPTO_ALGORITHM_RATCHET,
+ senderKey = olmDevice.deviceCurve25519Key!!,
+ forwardingCurve25519KeyChain = emptyList(),
+ keysClaimed = keysClaimedMap,
+ exportFormat = false,
+ sharedHistory = sharedHistory,
+ trusted = true
+ )
+
+ defaultKeysBackupService.maybeBackupKeys()
+
+ return MXOutboundSessionInfo(
+ sessionId = sessionId,
+ senderKey = olmDevice.deviceCurve25519Key!!,
+ sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore),
+ clock = clock,
+ sharedHistory = sharedHistory
+ )
+ }
+
+ /**
+ * Ensure the current session.
+ *
+ * @param devicesInRoom the devices list
+ */
+ private suspend fun ensureCurrentSession(devicesInRoom: MXUsersDevicesMap): MXOutboundSessionInfo {
+ Timber.tag(loggerTag.value).v("ensureCurrentSession roomId:$roomId")
+ val sharedHistory = cryptoStore.shouldShareHistory(roomId)
+ val safeSession: MXOutboundSessionInfo
+ val exportedSession: MegolmSessionData?
+
+ val outboundSession = this.outboundSession
+ if (outboundSession != null) {
+ Timber.tag(loggerTag.value).i("ensureCurrentSession() : reuse outbound session ${outboundSession.sessionId} in $roomId")
+ safeSession = outboundSession
+ val inboundGroupSessionHolder = olmDevice.getInboundGroupSession(outboundSession.sessionId, outboundSession.senderKey, roomId)
+ exportedSession = inboundGroupSessionHolder.mutex.withLock {
+ inboundGroupSessionHolder.wrapper.exportKeys()
+ }
+ } else {
+ val currentGroupSession = olmDevice.getCurrentGroupSession(roomId)
+ val sessionId = currentGroupSession?.wrapper?.safeSessionId
+ if (currentGroupSession != null && sessionId != null) {
+ Timber.tag(loggerTag.value).i("ensureCurrentSession() : reuse existing session $sessionId in $roomId")
+ safeSession = MXOutboundSessionInfo(
+ sessionId = sessionId,
+ senderKey = currentGroupSession.wrapper.senderKey,
+ sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore),
+ clock = clock,
+ sharedHistory = sharedHistory
+ )
+ exportedSession = currentGroupSession.mutex.withLock {
+ currentGroupSession.wrapper.exportKeys()
+ }
+ } else {
+ safeSession = prepareNewSessionInRoom()
+ exportedSession = null
+ Timber.tag(loggerTag.value).i("ensureCurrentSession() : use new session ${safeSession.sessionId} in $roomId")
+ }
+ this.outboundSession = safeSession
+ }
+
+ var remoteSessionMap: Map>? = null
+ if (safeSession.sharedSessionMap.isEmpty()) {
+ val sharedWithDevices = safeSession.sharedWithHelper.sharedWithDevices()
+ if (!sharedWithDevices.isEmpty) {
+ safeSession.sharedSessionMap = sharedWithDevices.map
+ } else {
+ remoteSessionMap = getSessionMapTask.execute(GetSessionMapTask.Params(roomId, safeSession.sessionId))
+ safeSession.sharedSessionMap = remoteSessionMap
+ }
+ }
+
+ val shareMap = HashMap>()/* userId */
+ val userIds = devicesInRoom.userIds
+ var skipCount = 0
+ for (userId in userIds) {
+ val deviceIds = devicesInRoom.getUserDeviceIds(userId)
+ for (deviceId in deviceIds!!) {
+ val deviceInfo = devicesInRoom.getObject(userId, deviceId)
+ if (deviceInfo != null && safeSession.sharedSessionMap[userId]?.get(deviceId) == null) {
+ val devices = shareMap.getOrPut(userId) { ArrayList() }
+ devices.add(deviceInfo)
+ } else {
+ skipCount++
+ }
+ }
+ }
+ val devicesCount = shareMap.entries.fold(0) { acc, new -> acc + new.value.size }
+ Timber.tag(loggerTag.value).d("roomId:$roomId found $devicesCount devices without megolm session(${safeSession.sessionId}), skip count: $skipCount")
+
+ safeSession.updatedUserDevices.clear()
+ // measure time consumed
+ val ts = clock.epochMillis()
+ shareKey(safeSession, exportedSession, shareMap)
+ Timber.tag(loggerTag.value).d("shareKey in $roomId done in ${clock.epochMillis() - ts} millis")
+
+ if (!remoteSessionMap.isNullOrEmpty()) {
+ cryptoStore.batchMarkedSessionAsShared(roomId, safeSession.sessionId, remoteSessionMap)
+ Timber.tag(loggerTag.value).d("batchMarkedSessionAsShared in $roomId for ${safeSession.sessionId} done in ${clock.epochMillis() - ts} millis")
+ }
+
+ if (safeSession.updatedUserDevices.isNotEmpty()) {
+ Timber.tag(loggerTag.value).d("update ${safeSession.updatedUserDevices.size} users for megolm session(${safeSession.sessionId})")
+ tryOrNull {
+ putSessionMapTask.execute(PutSessionMapTask.Params(roomId, safeSession.sessionId, safeSession.updatedUserDevices))
+ }
+ Timber.tag(loggerTag.value).d("PutSessionMapTask in $roomId for ${safeSession.sessionId} done in ${clock.epochMillis() - ts} millis")
+ }
+
+ return safeSession
+ }
+
+ /**
+ * Share the device key to a list of users.
+ *
+ * @param session the session info
+ * @param devicesByUsers the devices map
+ */
+ private suspend fun shareKey(
+ session: MXOutboundSessionInfo,
+ export: MegolmSessionData?,
+ devicesByUsers: Map>
+ ) {
+ // nothing to send, the task is done
+ if (devicesByUsers.isEmpty()) {
+ Timber.tag(loggerTag.value).v("shareKey() : nothing more to do")
+ return
+ }
+ // reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
+ val subMap = HashMap>()
+ var devicesCount = 0
+ for ((userId, devices) in devicesByUsers) {
+ subMap[userId] = devices
+ devicesCount += devices.size
+ if (devicesCount > 100) {
+ break
+ }
+ }
+
+ Timber.tag(loggerTag.value).v("shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
+ shareUserDevicesKey(session, export, subMap)
+ val remainingDevices = devicesByUsers - subMap.keys
+ shareKey(session, export, remainingDevices)
+ }
+
+ /**
+ * Share the device keys of a an user.
+ *
+ * @param sessionInfo the session info
+ * @param devicesByUser the devices map
+ */
+ private suspend fun shareUserDevicesKey(
+ sessionInfo: MXOutboundSessionInfo,
+ exportedSessionData: MegolmSessionData?,
+ devicesByUser: Map>
+ ) {
+ val chainIndex: Int
+
+ val payload = if (exportedSessionData != null) {
+ chainIndex = 0
+ mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to exportedSessionData
+ )
+ } else {
+ chainIndex = olmDevice.getMessageIndex(sessionInfo.sessionId)
+ val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also {
+ Timber.tag(loggerTag.value).e("shareUserDevicesKey() Failed to share session, failed to export")
+ }
+ mapOf(
+ "type" to EventType.ROOM_KEY,
+ "content" to mapOf(
+ "algorithm" to MXCRYPTO_ALGORITHM_RATCHET,
+ "room_id" to roomId,
+ "session_id" to sessionInfo.sessionId,
+ "session_key" to sessionKey,
+ "chain_index" to chainIndex,
+ "org.matrix.msc3061.shared_history" to sessionInfo.sharedHistory
+ )
+ )
+ }
+
+ var t0 = clock.epochMillis()
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
+
+ val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+ Timber.tag(loggerTag.value).v(
+ """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${clock.epochMillis() - t0} ms"""
+ .trimMargin()
+ )
+ val contentMap = MXUsersDevicesMap()
+ var haveTargets = false
+ val userIds = results.userIds
+ val noOlmToNotify = mutableListOf()
+ for (userId in userIds) {
+ val devicesToShareWith = devicesByUser[userId]
+ for ((deviceID) in devicesToShareWith!!) {
+ val sessionResult = results.getObject(userId, deviceID)
+ if (sessionResult?.sessionId == null) {
+ // no session with this device, probably because there
+ // were no one-time keys.
+
+ // MSC 2399
+ // send withheld m.no_olm: an olm session could not be established.
+ // This may happen, for example, if the sender was unable to obtain a one-time key from the recipient.
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : No Olm Session for $userId:$deviceID mark for withheld")
+ noOlmToNotify.add(UserDevice(userId, deviceID))
+ continue
+ }
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
+ val encryptedMessage = messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo))
+ encryptedMessage.traceId = sessionInfo.sessionId
+ encryptedMessage.roomId = roomId
+ contentMap.setObject(userId, deviceID, encryptedMessage)
+ haveTargets = true
+ }
+ }
+
+ // Add the devices we have shared with to session.sharedWithDevices.
+ // we deliberately iterate over devicesByUser (ie, the devices we
+ // attempted to share with) rather than the contentMap (those we did
+ // share with), because we don't want to try to claim a one-time-key
+ // for dead devices on every message.
+ for ((_, devicesToShareWith) in devicesByUser) {
+ for (deviceInfo in devicesToShareWith) {
+ sessionInfo.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex)
+ val devices = sessionInfo.updatedUserDevices.getOrPut(deviceInfo.userId) { HashMap() }
+ devices[deviceInfo.deviceId] = 1
+ // XXX is it needed to add it to the audit trail?
+ // For now decided that no, we are more interested by forward trail
+ }
+ }
+
+ if (haveTargets) {
+ t0 = clock.epochMillis()
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${sessionInfo.sessionId} : has target")
+ Timber.tag(loggerTag.value).d("sending to device room key for ${sessionInfo.sessionId} to ${contentMap.toDebugString()}")
+ val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
+ try {
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.execute(sendToDeviceParams)
+ }
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms")
+ } catch (failure: Throwable) {
+ // What to do here...
+ Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${sessionInfo.sessionId}>")
+ }
+ } else {
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key")
+ }
+
+ if (noOlmToNotify.isNotEmpty()) {
+ // XXX offload?, as they won't read the message anyhow?
+ notifyKeyWithHeld(
+ noOlmToNotify,
+ sessionInfo.sessionId,
+ olmDevice.deviceCurve25519Key,
+ WithHeldCode.NO_OLM
+ )
+ }
+ }
+
+ private suspend fun notifyKeyWithHeld(
+ targets: List,
+ sessionId: String,
+ senderKey: String?,
+ code: WithHeldCode
+ ) {
+ Timber.tag(loggerTag.value).d(
+ "notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
+ " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}"
+ )
+ val withHeldContent = RoomKeyWithHeldContent(
+ roomId = roomId,
+ senderKey = senderKey,
+ algorithm = MXCRYPTO_ALGORITHM_RATCHET,
+ sessionId = sessionId,
+ codeString = code.value,
+ fromDevice = myDeviceId
+ )
+ val params = SendToDeviceTask.Params(
+ EventType.ROOM_KEY_WITHHELD.stable,
+ MXUsersDevicesMap().apply {
+ targets.forEach {
+ setObject(it.userId, it.deviceId, withHeldContent)
+ }
+ }
+ )
+ try {
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.execute(params)
+ }
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value)
+ .e("notifyKeyWithHeld() :$sessionId Failed to send withheld ${targets.map { "${it.userId}|${it.deviceId}" }}")
+ }
+ }
+
+ /**
+ * process the pending encryptions.
+ */
+ private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
+ // Everything is in place, encrypt all pending events
+ val payloadJson = HashMap()
+ payloadJson["room_id"] = roomId
+ payloadJson["type"] = eventType
+ payloadJson["content"] = eventContent
+
+ val sessionWrapper = olmDevice.getInboundGroupSession(session.sessionId, session.senderKey, roomId).wrapper
+ val currentSession = sessionWrapper.session
+ if (currentSession.sessionIdentifier() != session.sessionId) {
+ throw Exception("current session $session.sessionId is invalid for room: $roomId")
+ }
+
+ val internalKey = currentSession.export(currentSession.firstKnownIndex)
+ val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
+ Timber.i("aes decrypt with sessionId: ${session.sessionId}, key: $internalKey, cleartext: $payloadString")
+ val ciphertext = AESUtil.encrypt(internalKey.toByteArray().copyOfRange(0, 16), payloadString)
+
+ val map = HashMap()
+ map["algorithm"] = MXCRYPTO_ALGORITHM_RATCHET
+ map["sender_key"] = sessionWrapper.senderKey ?: ""
+ map["ciphertext"] = ciphertext
+ map["session_id"] = session.sessionId
+
+ session.useCount++
+ return map
+ }
+
+ /**
+ * Get the list of devices which can encrypt data to.
+ * This method must be called in getDecryptingThreadHandler() thread.
+ *
+ * @param userIds the user ids whose devices must be checked.
+ * @param forceDistributeToUnverified If true the unverified devices will be included in valid recipients even if
+ * such devices are blocked in crypto settings
+ */
+ private suspend fun getDevicesInRoom(userIds: List, forceDistributeToUnverified: Boolean = false): DeviceInRoomInfo {
+ // We are happy to use a cached version here: we assume that if we already
+ // have a list of the user's devices, then we already share an e2e room
+ // with them, which means that they will have announced any new devices via
+ // an m.new_device.
+ val keys = deviceListManager.downloadKeys(userIds, false)
+ val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() ||
+ cryptoStore.getBlockUnverifiedDevices(roomId)
+
+ val devicesInRoom = DeviceInRoomInfo()
+ val unknownDevices = MXUsersDevicesMap()
+
+ for (userId in keys.userIds) {
+ val deviceIds = keys.getUserDeviceIds(userId) ?: continue
+ for (deviceId in deviceIds) {
+ val deviceInfo = keys.getObject(userId, deviceId) ?: continue
+ if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
+ // The device is not yet known by the user
+ unknownDevices.setObject(userId, deviceId, deviceInfo)
+ continue
+ }
+ if (deviceInfo.isBlocked) {
+ // Remove any blocked devices
+ devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.BLACKLISTED)
+ continue
+ }
+
+ if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly && !forceDistributeToUnverified) {
+ devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.UNVERIFIED)
+ continue
+ }
+
+ if (deviceInfo.identityKey() == olmDevice.deviceCurve25519Key) {
+ // Don't bother sending to ourself
+ continue
+ }
+ devicesInRoom.allowedDevices.setObject(userId, deviceId, deviceInfo)
+ }
+ }
+ if (unknownDevices.isEmpty) {
+ return devicesInRoom
+ } else {
+ throw MXCryptoError.UnknownDevice(unknownDevices)
+ }
+ }
+
+ override suspend fun reshareKey(
+ groupSessionId: String,
+ userId: String,
+ deviceId: String,
+ senderKey: String
+ ): Boolean {
+ Timber.tag(loggerTag.value).i("process reshareKey for $groupSessionId to $userId:$deviceId")
+ val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false
+ .also { Timber.tag(loggerTag.value).w("reshareKey: Device not found") }
+
+ // Get the chain index of the key we previously sent this device
+ val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, groupSessionId, deviceInfo)
+ if (!wasSessionSharedWithUser.found) {
+ // This session was never shared with this user
+ // Send a room key with held
+ notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), groupSessionId, senderKey, WithHeldCode.UNAUTHORISED)
+ Timber.tag(loggerTag.value).w("reshareKey: ERROR : Never shared megolm with this device")
+ return false
+ }
+ // if found chain index should not be null
+ val chainIndex = wasSessionSharedWithUser.chainIndex ?: return false
+ .also {
+ Timber.tag(loggerTag.value).w("reshareKey: Null chain index")
+ }
+
+ val devicesByUser = mapOf(userId to listOf(deviceInfo))
+ val usersDeviceMap = try {
+ ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+ } catch (failure: Throwable) {
+ null
+ }
+ val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId)
+ if (olmSessionResult?.sessionId == null) {
+ Timber.tag(loggerTag.value).w("reshareKey: no session with this device, probably because there were no one-time keys")
+ return false
+ }
+ Timber.tag(loggerTag.value).i(" reshareKey: $groupSessionId:$chainIndex with device $userId:$deviceId using session ${olmSessionResult.sessionId}")
+
+ val sessionHolder = try {
+ olmDevice.getInboundGroupSession(groupSessionId, senderKey, roomId)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice: failed to get session $groupSessionId")
+ return false
+ }
+
+ val export = sessionHolder.mutex.withLock {
+ sessionHolder.wrapper.exportKeys()
+ } ?: return false.also {
+ Timber.tag(loggerTag.value).e("shareKeysWithDevice: failed to export group session $groupSessionId")
+ }
+
+ val payloadJson = mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to export
+ )
+
+ val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
+ encodedPayload.traceId = groupSessionId
+ val sendToDeviceMap = MXUsersDevicesMap()
+ sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
+ Timber.tag(loggerTag.value).i("reshareKey() : sending session $groupSessionId to $userId:$deviceId")
+ val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+ return try {
+ sendToDeviceTask.execute(sendToDeviceParams)
+ Timber.tag(loggerTag.value).i("reshareKey() : successfully send <$groupSessionId> to $userId:$deviceId")
+ true
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).e(failure, "reshareKey() : fail to send <$groupSessionId> to $userId:$deviceId")
+ false
+ }
+ }
+
+ @Throws
+ override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {
+ require(inboundSessionWrapper.wrapper.sessionData.sharedHistory) { "This key can't be shared" }
+ Timber.tag(loggerTag.value)
+ .i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
+ val userId = deviceInfo.userId
+ val deviceId = deviceInfo.deviceId
+ val devicesByUser = mapOf(userId to listOf(deviceInfo))
+ val usersDeviceMap = try {
+ ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).i(failure, "process shareHistoryKeys failed to ensure olm")
+ // process anyway?
+ null
+ }
+ val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId)
+ if (olmSessionResult?.sessionId == null) {
+ Timber.tag(loggerTag.value)
+ .w("shareHistoryKeys: no session with this device, probably because there were no one-time keys")
+ return
+ }
+
+ val export = inboundSessionWrapper.mutex.withLock {
+ inboundSessionWrapper.wrapper.exportKeys()
+ } ?: return Unit.also {
+ Timber.tag(loggerTag.value)
+ .e("shareHistoryKeys: failed to export group session ${inboundSessionWrapper.wrapper.safeSessionId}")
+ }
+
+ val payloadJson = mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to export
+ )
+
+ val encodedPayload =
+ withContext(coroutineDispatchers.computation) {
+ messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
+ }
+ val sendToDeviceMap = MXUsersDevicesMap()
+ sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
+ Timber.tag(loggerTag.value)
+ .d("shareHistoryKeys() : sending session ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
+ val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.execute(sendToDeviceParams)
+ }
+ }
+
+ data class DeviceInRoomInfo(
+ val allowedDevices: MXUsersDevicesMap = MXUsersDevicesMap(),
+ val withHeldDevices: MXUsersDevicesMap = MXUsersDevicesMap()
+ )
+
+ data class UserDevice(
+ val userId: String,
+ val deviceId: String
+ )
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetEncryptionFactory.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetEncryptionFactory.kt
new file mode 100644
index 0000000..193b0f9
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/megolm/MXRatchetEncryptionFactory.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.algorithms.megolm
+
+import kotlinx.coroutines.CoroutineScope
+import org.sdn.android.sdk.api.SDNCoroutineDispatchers
+import org.sdn.android.sdk.internal.crypto.DeviceListManager
+import org.sdn.android.sdk.internal.crypto.MXOlmDevice
+import org.sdn.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
+import org.sdn.android.sdk.internal.crypto.actions.MessageEncrypter
+import org.sdn.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
+import org.sdn.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
+import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.sdn.android.sdk.internal.crypto.tasks.GetSessionMapTask
+import org.sdn.android.sdk.internal.crypto.tasks.PutSessionMapTask
+import org.sdn.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.sdn.android.sdk.internal.di.DeviceId
+import org.sdn.android.sdk.internal.di.UserId
+import org.sdn.android.sdk.internal.util.time.Clock
+import javax.inject.Inject
+
+internal class MXRatchetEncryptionFactory @Inject constructor(
+ private val olmDevice: MXOlmDevice,
+ private val defaultKeysBackupService: DefaultKeysBackupService,
+ private val cryptoStore: IMXCryptoStore,
+ private val deviceListManager: DeviceListManager,
+ private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
+ @UserId private val userId: String,
+ @DeviceId private val deviceId: String?,
+ private val sendToDeviceTask: SendToDeviceTask,
+ private val getSessionMapTask: GetSessionMapTask,
+ private val putSessionMapTask: PutSessionMapTask,
+ private val messageEncrypter: MessageEncrypter,
+ private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
+ private val coroutineDispatchers: SDNCoroutineDispatchers,
+ private val cryptoCoroutineScope: CoroutineScope,
+ private val clock: Clock,
+) {
+
+ fun create(roomId: String): MXRatchetEncryption {
+ return MXRatchetEncryption(
+ roomId = roomId,
+ olmDevice = olmDevice,
+ defaultKeysBackupService = defaultKeysBackupService,
+ cryptoStore = cryptoStore,
+ deviceListManager = deviceListManager,
+ ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
+ myUserId = userId,
+ myDeviceId = deviceId!!,
+ sendToDeviceTask = sendToDeviceTask,
+ getSessionMapTask = getSessionMapTask,
+ putSessionMapTask = putSessionMapTask,
+ messageEncrypter = messageEncrypter,
+ warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
+ coroutineDispatchers = coroutineDispatchers,
+ cryptoCoroutineScope = cryptoCoroutineScope,
+ clock = clock,
+ )
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
index c673760..df86741 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
@@ -21,6 +21,7 @@ import org.sdn.android.sdk.api.logger.LoggerTag
import org.sdn.android.sdk.api.session.crypto.MXCryptoError
import org.sdn.android.sdk.api.session.crypto.model.MXEventDecryptionResult
import org.sdn.android.sdk.api.session.events.model.Event
+import org.sdn.android.sdk.api.session.events.model.EventType
import org.sdn.android.sdk.api.session.events.model.content.OlmEventContent
import org.sdn.android.sdk.api.session.events.model.content.OlmPayloadContent
import org.sdn.android.sdk.api.session.events.model.toModel
@@ -77,7 +78,13 @@ internal class MXOlmDecryption(
@Suppress("UNCHECKED_CAST")
val message = messageAny as JsonDict
- val decryptedPayload = decryptMessage(message, senderKey)
+ val decryptedPayload =
+ if (event.type == EventType.ROOM_KEY_REPLY) {
+ decryptKeyReply(message, senderKey)
+ } else {
+ decryptMessage(message, senderKey)
+ }
+// val decryptedPayload = decryptMessage(message, senderKey)
if (decryptedPayload == null) {
Timber.tag(loggerTag.value).e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
@@ -115,25 +122,26 @@ internal class MXOlmDecryption(
)
}
- val recipientKeys = olmPayloadContent.recipientKeys ?: run {
- Timber.tag(loggerTag.value).e(
+ if (event.type != EventType.ROOM_KEY_REPLY) {
+ val recipientKeys = olmPayloadContent.recipientKeys ?: run {
+ Timber.tag(loggerTag.value).e(
"## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
" property; cannot prevent unknown-key attack"
- )
- throw MXCryptoError.Base(
+ )
+ throw MXCryptoError.Base(
MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")
- )
- }
-
- val ed25519 = recipientKeys["ed25519"]
+ )
+ }
- if (ed25519 != olmDevice.deviceEd25519Key) {
- Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
- throw MXCryptoError.Base(
+ val ed25519 = recipientKeys["ed25519"]
+ if (ed25519 != olmDevice.deviceEd25519Key) {
+ Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
+ throw MXCryptoError.Base(
MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.BAD_RECIPIENT_KEY_REASON
- )
+ )
+ }
}
if (olmPayloadContent.sender.isNullOrBlank()) {
@@ -154,14 +162,14 @@ internal class MXOlmDecryption(
)
}
- if (olmPayloadContent.roomId != event.roomId) {
- Timber.tag(loggerTag.value)
- .e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
- throw MXCryptoError.Base(
- MXCryptoError.ErrorType.BAD_ROOM,
- String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId)
- )
- }
+// if (olmPayloadContent.roomId != event.roomId) {
+// Timber.tag(loggerTag.value)
+// .e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
+// throw MXCryptoError.Base(
+// MXCryptoError.ErrorType.BAD_ROOM,
+// String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId)
+// )
+// }
val keys = olmPayloadContent.keys ?: run {
Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys")
@@ -178,6 +186,17 @@ internal class MXOlmDecryption(
)
}
+ private suspend fun decryptKeyReply(message: JsonDict, theirDeviceIdentityKey: String): String? {
+ val messageBody = message["body"] as? String ?: return null
+ val messageType = when (val typeAsVoid = message["type"]) {
+ is Double -> typeAsVoid.toInt()
+ is Int -> typeAsVoid
+ is Long -> typeAsVoid.toInt()
+ else -> return null
+ }
+ return this.olmDevice.olmDecryptMessage(messageBody, messageType)
+ }
+
/**
* Attempt to decrypt an Olm message.
*
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/api/CryptoApi.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/api/CryptoApi.kt
index 7f2b42e..c23f5ee 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/api/CryptoApi.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/api/CryptoApi.kt
@@ -17,6 +17,7 @@ package org.sdn.android.sdk.internal.crypto.api
import org.sdn.android.sdk.api.session.crypto.model.DeviceInfo
import org.sdn.android.sdk.api.session.crypto.model.DevicesListResponse
+import org.sdn.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.sdn.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
import org.sdn.android.sdk.internal.crypto.model.rest.DeleteDevicesParams
import org.sdn.android.sdk.internal.crypto.model.rest.KeyChangesResponse
@@ -26,6 +27,7 @@ import org.sdn.android.sdk.internal.crypto.model.rest.KeysQueryBody
import org.sdn.android.sdk.internal.crypto.model.rest.KeysQueryResponse
import org.sdn.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.sdn.android.sdk.internal.crypto.model.rest.KeysUploadResponse
+import org.sdn.android.sdk.internal.crypto.model.rest.PullKeysBody
import org.sdn.android.sdk.internal.crypto.model.rest.SendToDeviceBody
import org.sdn.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.sdn.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody
@@ -173,4 +175,22 @@ internal interface CryptoApi {
@Query("from") oldToken: String,
@Query("to") newToken: String
): KeyChangesResponse
+
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "get_olm_event")
+ suspend fun pullRoomKeys(@Body body: PullKeysBody): ToDeviceSyncResponse
+
+ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{room_id}/session/{session_id}")
+ @JvmSuppressWildcards
+ suspend fun getSessionMap(
+ @Path("room_id") roomId: String,
+ @Path("session_id") sessionId: String,
+ ): Map>
+
+ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{room_id}/session/{session_id}")
+ @JvmSuppressWildcards
+ suspend fun putSessionMap(
+ @Path("room_id") roomId: String,
+ @Path("session_id") sessionId: String,
+ @Body body: Map>
+ )
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/InboundGroupSessionData.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
index 4db53dc..1dc4fc6 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
@@ -26,6 +26,10 @@ data class InboundGroupSessionData(
@Json(name = "room_id")
var roomId: String? = null,
+ /** The algorithm in which this session is used. */
+ @Json(name = "algorithm")
+ var algorithm: String? = null,
+
/** The base64-encoded curve25519 key of the sender. */
@Json(name = "sender_key")
var senderKey: String? = null,
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
index 43387f0..2687cd3 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
@@ -16,7 +16,6 @@
package org.sdn.android.sdk.internal.crypto.model
-import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.api.extensions.tryOrNull
import org.sdn.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
@@ -51,7 +50,7 @@ data class MXInboundMegolmSessionWrapper(
roomId = sessionData.roomId,
sessionId = session.sessionIdentifier(),
senderKey = senderKey,
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ algorithm = sessionData.algorithm,
sharedHistory = sessionData.sharedHistory
)
} catch (e: Exception) {
@@ -82,6 +81,7 @@ data class MXInboundMegolmSessionWrapper(
}
val data = InboundGroupSessionData(
roomId = megolmSessionData.roomId,
+ algorithm = megolmSessionData.algorithm,
senderKey = megolmSessionData.senderKey,
keysClaimed = megolmSessionData.senderClaimedKeys,
forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain,
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt
index fd367c2..e237c45 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt
@@ -16,7 +16,6 @@
package org.sdn.android.sdk.internal.crypto.model
-import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
@@ -34,6 +33,9 @@ internal class OlmInboundGroupSessionWrapper : Serializable {
// The room in which this session is used.
var roomId: String? = null
+ // The algorithm in which this session is used.
+ var algorithm: String? = null
+
// The base64-encoded curve25519 key of the sender.
var senderKey: String? = null
@@ -95,6 +97,7 @@ internal class OlmInboundGroupSessionWrapper : Serializable {
senderKey = megolmSessionData.senderKey
keysClaimed = megolmSessionData.senderClaimedKeys
roomId = megolmSessionData.roomId
+ roomId = megolmSessionData.algorithm
} catch (e: Exception) {
throw Exception(e.message)
}
@@ -123,7 +126,7 @@ internal class OlmInboundGroupSessionWrapper : Serializable {
roomId = roomId,
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex),
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM
+ algorithm = algorithm
)
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
index 3191249..3878965 100755
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
@@ -16,7 +16,6 @@
package org.sdn.android.sdk.internal.crypto.model
-import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
@@ -36,6 +35,9 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable {
// The room in which this session is used.
var roomId: String? = null
+ // The algorithm in which this session is used.
+ var algorithm: String? = null
+
// The base64-encoded curve25519 key of the sender.
var senderKey: String? = null
@@ -100,6 +102,7 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable {
senderKey = megolmSessionData.senderKey
keysClaimed = megolmSessionData.senderClaimedKeys
roomId = megolmSessionData.roomId
+ algorithm = megolmSessionData.algorithm
} catch (e: Exception) {
throw Exception(e.message)
}
@@ -133,7 +136,7 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable {
roomId = roomId,
sessionId = safeOlmInboundGroupSession.sessionIdentifier(),
sessionKey = safeOlmInboundGroupSession.export(wantedIndex),
- algorithm = MXCRYPTO_ALGORITHM_MEGOLM
+ algorithm = algorithm
)
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt
index 9b2f7d0..da6b22e 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt
@@ -28,6 +28,12 @@ internal data class EncryptedMessage(
@Json(name = "sender_key")
val senderKey: String? = null,
+ @Json(name = "trace_id")
+ var traceId: String? = null,
+
+ @Json(name = "room_id")
+ var roomId: String? = null,
+
@Json(name = "ciphertext")
val cipherText: Map? = null
) : SendToDeviceObject
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/PullKeysBody.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/PullKeysBody.kt
new file mode 100644
index 0000000..0b897f8
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/model/rest/PullKeysBody.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.model.rest
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class PullKeysBody (
+ @Json(name = "trace_id")
+ val traceId: String,
+)
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index b1272bf..5df5f99 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -42,6 +42,7 @@ import org.sdn.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
import org.sdn.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmOutboundGroupSession
+import org.sdn.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
/**
* The crypto data store.
@@ -264,6 +265,8 @@ internal interface IMXCryptoStore {
fun saveMyDevicesInfo(info: List)
+ fun getAllCryptoRooms(): List
+
/**
* Store the crypto algorithm for a room.
*
@@ -382,6 +385,27 @@ internal interface IMXCryptoStore {
*/
fun removeInboundGroupSession(sessionId: String, senderKey: String)
+ /**
+ * Retrieve the current group session.
+ *
+ * @param roomId the room ID.
+ * @return the current group session.
+ */
+ fun getCurrentGroupSession(roomId: String): MXInboundMegolmSessionWrapper?
+
+ /**
+ * Remove current group session.
+ *
+ * @param roomId the room ID.
+ */
+ fun removeCurrentGroupSession(roomId: String)
+
+ /**
+ * Remove all current group session.
+ *
+ */
+ fun removeAllCurrentGroupSession(roomIds: Array)
+
/* ==========================================================================================
* Keys backup
* ========================================================================================== */
@@ -438,7 +462,7 @@ internal interface IMXCryptoStore {
*/
fun getOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody): OutgoingKeyRequest?
fun getOutgoingRoomKeyRequest(requestId: String): OutgoingKeyRequest?
- fun getOutgoingRoomKeyRequest(roomId: String, sessionId: String, algorithm: String, senderKey: String): List
+ fun getOutgoingRoomKeyRequest(roomId: String, sessionId: String, senderKey: String): List
/**
* Look for an existing outgoing room key request, and if none is found, add a new one.
@@ -554,7 +578,15 @@ internal interface IMXCryptoStore {
userId: String,
deviceId: String,
deviceIdentityKey: String,
- chainIndex: Int
+ chainIndex: Int,
+ lastUpdate: Long? = 0,
+ directShare: Boolean? = false
+ )
+
+ fun batchMarkedSessionAsShared(
+ roomId: String?,
+ sessionId: String,
+ sessionMap: Map>
)
/**
@@ -566,6 +598,15 @@ internal interface IMXCryptoStore {
*/
fun getSharedSessionInfo(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): SharedSessionResult
data class SharedSessionResult(val found: Boolean, val chainIndex: Int?)
+ data class SharedSessionDetail(val roomId: String?,
+ val sessionId: String?,
+ val userId: String?,
+ val deviceId: String?,
+ val deviceIdentityKey: String?,
+ val lastUpdate: Long?,
+ val directShare: Boolean?)
+
+ fun getSharedSessionDetail(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): SharedSessionDetail?
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap
// Dev tools
@@ -580,4 +621,6 @@ internal interface IMXCryptoStore {
fun areDeviceKeysUploaded(): Boolean
fun tidyUpDataBase()
fun getOutgoingRoomKeyRequests(inStates: Set): List
+ fun storeFbk25519(fbk25519: String)
+ fun getFbk25519(): String
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index baf1378..25a3f0d 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -27,6 +27,7 @@ import io.realm.Sort
import io.realm.kotlin.createObject
import io.realm.kotlin.where
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.extensions.tryOrNull
import org.sdn.android.sdk.api.logger.LoggerTag
import org.sdn.android.sdk.api.session.crypto.GlobalCryptoConfig
@@ -101,6 +102,8 @@ import org.sdn.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException
import org.matrix.olm.OlmOutboundGroupSession
+import org.sdn.android.sdk.internal.crypto.store.db.model.CurrentGroupSessionEntity
+import org.sdn.android.sdk.internal.crypto.store.db.model.CurrentGroupSessionEntityFields
import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@@ -666,6 +669,16 @@ internal class RealmCryptoStore @Inject constructor(
}
}
+ override fun getAllCryptoRooms(): List {
+ return doWithRealm(realmConfiguration) {
+ it.where()
+ .findAll()
+ .mapNotNull { cryptoRoom ->
+ cryptoRoom.roomId
+ }
+ }
+ }
+
override fun storeRoomAlgorithm(roomId: String, algorithm: String?) {
doRealmTransaction(realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).let { entity ->
@@ -673,7 +686,7 @@ internal class RealmCryptoStore @Inject constructor(
// store anyway the new algorithm, but mark the room
// as having been encrypted once whatever, this can never
// go back to false
- if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
+ if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM || algorithm == MXCRYPTO_ALGORITHM_RATCHET) {
entity.wasEncryptedOnce = true
}
}
@@ -817,6 +830,10 @@ internal class RealmCryptoStore @Inject constructor(
Timber.v("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key")
realm.insertOrUpdate(realmOlmInboundGroupSession)
+ realm.insertOrUpdate(CurrentGroupSessionEntity().apply {
+ store(wrapper)
+ backedUp = existing?.backedUp ?: false
+ })
}
}
}
@@ -922,6 +939,33 @@ internal class RealmCryptoStore @Inject constructor(
}
}
+ override fun getCurrentGroupSession(roomId: String): MXInboundMegolmSessionWrapper? {
+ return doWithRealm(realmConfiguration) { realm ->
+ realm.where()
+ .equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId)
+ .findFirst()
+ ?.toModel()
+ }
+ }
+
+ override fun removeCurrentGroupSession(roomId: String) {
+ doRealmTransaction(realmConfiguration) {
+ it.where()
+ .equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId)
+ .findAll()
+ .deleteAllFromRealm()
+ }
+ }
+
+ override fun removeAllCurrentGroupSession(roomIds: Array) {
+ doRealmTransaction(realmConfiguration) {
+ it.where()
+ .`in`(CurrentGroupSessionEntityFields.ROOM_ID, roomIds)
+ .findAll()
+ .deleteAllFromRealm()
+ }
+ }
+
/* ==========================================================================================
* Keys backup
* ========================================================================================== */
@@ -1162,8 +1206,7 @@ internal class RealmCryptoStore @Inject constructor(
}.map {
it.toOutgoingKeyRequest()
}.firstOrNull {
- it.requestBody?.algorithm == requestBody.algorithm &&
- it.requestBody?.roomId == requestBody.roomId &&
+ it.requestBody?.roomId == requestBody.roomId &&
it.requestBody?.senderKey == requestBody.senderKey &&
it.requestBody?.sessionId == requestBody.sessionId
}
@@ -1178,7 +1221,7 @@ internal class RealmCryptoStore @Inject constructor(
}.firstOrNull()
}
- override fun getOutgoingRoomKeyRequest(roomId: String, sessionId: String, algorithm: String, senderKey: String): List {
+ override fun getOutgoingRoomKeyRequest(roomId: String, sessionId: String, senderKey: String): List {
// TODO this annoying we have to load all
return monarchy.fetchAllCopiedSync { realm ->
realm.where()
@@ -1187,8 +1230,7 @@ internal class RealmCryptoStore @Inject constructor(
}.map {
it.toOutgoingKeyRequest()
}.filter {
- it.requestBody?.algorithm == algorithm &&
- it.requestBody?.senderKey == senderKey
+ it.requestBody?.senderKey == senderKey
}
}
@@ -1283,8 +1325,7 @@ internal class RealmCryptoStore @Inject constructor(
}
}
.firstOrNull {
- it.requestBody?.algorithm == requestBody.algorithm &&
- it.requestBody?.sessionId == requestBody.sessionId &&
+ it.requestBody?.sessionId == requestBody.sessionId &&
it.requestBody?.senderKey == requestBody.senderKey &&
it.requestBody?.roomId == requestBody.roomId
}
@@ -1343,8 +1384,7 @@ internal class RealmCryptoStore @Inject constructor(
.equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId)
.findAll().firstOrNull { entity ->
entity.toOutgoingKeyRequest().let {
- it.requestBody?.senderKey == senderKey &&
- it.requestBody?.algorithm == algorithm
+ it.requestBody?.senderKey == senderKey
}
}?.apply {
event.senderId?.let { addReply(it, fromDevice, event) }
@@ -1712,7 +1752,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun addWithHeldMegolmSession(withHeldContent: RoomKeyWithHeldContent) {
val roomId = withHeldContent.roomId ?: return
val sessionId = withHeldContent.sessionId ?: return
- if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return
+ if (!arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(withHeldContent.algorithm)) return
doRealmTransaction(realmConfiguration) { realm ->
WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let {
it.code = withHeldContent.code
@@ -1743,7 +1783,9 @@ internal class RealmCryptoStore @Inject constructor(
userId: String,
deviceId: String,
deviceIdentityKey: String,
- chainIndex: Int
+ chainIndex: Int,
+ lastUpdate: Long?,
+ directShare: Boolean?
) {
doRealmTransaction(realmConfiguration) { realm ->
SharedSessionEntity.create(
@@ -1753,11 +1795,52 @@ internal class RealmCryptoStore @Inject constructor(
userId = userId,
deviceId = deviceId,
deviceIdentityKey = deviceIdentityKey,
- chainIndex = chainIndex
+ chainIndex = chainIndex,
+ lastUpdate = lastUpdate,
+ directShare = directShare
)
}
}
+ override fun batchMarkedSessionAsShared(
+ roomId: String?,
+ sessionId: String,
+ sessionMap: Map>
+ ) {
+ val entityList = ArrayList()
+ for ((userId, deviceMap) in sessionMap) {
+ for (deviceId in deviceMap.keys) {
+ entityList.add(SharedSessionEntity(
+ roomId = roomId,
+ sessionId = sessionId,
+ userId = userId,
+ deviceId = deviceId,
+ deviceIdentityKey = "",
+ chainIndex = 0
+ ))
+ }
+ }
+ doRealmTransaction(realmConfiguration) { realm ->
+ realm.insert(entityList)
+ }
+ }
+
+ override fun getSharedSessionDetail(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): IMXCryptoStore.SharedSessionDetail? {
+ return doWithRealm(realmConfiguration) { realm ->
+ SharedSessionEntity.get(
+ realm = realm,
+ roomId = roomId,
+ sessionId = sessionId,
+ userId = deviceInfo.userId,
+ deviceId = deviceInfo.deviceId,
+ deviceIdentityKey = deviceInfo.identityKey()
+ )?.let {
+ IMXCryptoStore.SharedSessionDetail(roomId, sessionId, deviceInfo.userId,
+ deviceInfo.deviceId, deviceInfo.identityKey(), it.lastUpdate, it.directShare )
+ }
+ }
+ }
+
override fun getSharedSessionInfo(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): IMXCryptoStore.SharedSessionResult {
return doWithRealm(realmConfiguration) { realm ->
SharedSessionEntity.get(
@@ -1815,4 +1898,16 @@ internal class RealmCryptoStore @Inject constructor(
// Can we do something for WithHeldSessionEntity?
}
}
+
+ override fun storeFbk25519(fbk25519: String) {
+ doRealmTransaction(realmConfiguration) {
+ it.where().findFirst()?.fbk25519 = fbk25519
+ }
+ }
+
+ override fun getFbk25519(): String {
+ return doWithRealm(realmConfiguration) {
+ it.where().findFirst()?.fbk25519
+ } ?: ""
+ }
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index 5fb4f86..c19e127 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -37,6 +37,8 @@ import org.sdn.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017
import org.sdn.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018
import org.sdn.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo019
import org.sdn.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo020
+import org.sdn.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo021
+import org.sdn.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo022
import org.sdn.android.sdk.internal.util.database.MatrixRealmMigration
import org.sdn.android.sdk.internal.util.time.Clock
import javax.inject.Inject
@@ -51,7 +53,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(
private val clock: Clock,
) : MatrixRealmMigration(
dbName = "Crypto",
- schemaVersion = 20L,
+ schemaVersion = 22L,
) {
/**
* Forces all RealmCryptoStoreMigration instances to be equal.
@@ -81,5 +83,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(
if (oldVersion < 18) MigrateCryptoTo018(realm).perform()
if (oldVersion < 19) MigrateCryptoTo019(realm).perform()
if (oldVersion < 20) MigrateCryptoTo020(realm).perform()
+ if (oldVersion < 21) MigrateCryptoTo021(realm).perform()
+ if (oldVersion < 22) MigrateCryptoTo022(realm).perform()
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
index 32396e2..3ab15a9 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
@@ -21,6 +21,7 @@ import org.sdn.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
+import org.sdn.android.sdk.internal.crypto.store.db.model.CurrentGroupSessionEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.KeyInfoEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntity
@@ -46,6 +47,7 @@ import org.sdn.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
DeviceInfoEntity::class,
KeysBackupDataEntity::class,
OlmInboundGroupSessionEntity::class,
+ CurrentGroupSessionEntity::class,
OlmSessionEntity::class,
UserEntity::class,
KeyInfoEntity::class,
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
index b7c0895..0225aa7 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
@@ -18,6 +18,7 @@ package org.sdn.android.sdk.internal.crypto.store.db.migration
import io.realm.DynamicRealm
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.sdn.android.sdk.internal.util.database.RealmMigrator
@@ -30,7 +31,7 @@ internal class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15
?.setNullable(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, true)
?.transform {
val currentAlgorithm = it.getString(CryptoRoomEntityFields.ALGORITHM)
- it.set(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, currentAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM)
+ it.set(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, currentAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM || currentAlgorithm == MXCRYPTO_ALGORITHM_RATCHET)
}
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt
new file mode 100644
index 0000000..a900868
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 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.sdn.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.sdn.android.sdk.internal.crypto.store.db.model.CurrentGroupSessionEntityFields
+import org.sdn.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * This migration create a new schema of CurrentGroupSessionEntity.
+ */
+internal class MigrateCryptoTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("CurrentGroupSessionEntity")
+ .addField(CurrentGroupSessionEntityFields.SESSION_ID, String::class.java)
+ .addField(CurrentGroupSessionEntityFields.SENDER_KEY, String::class.java)
+ .addField(CurrentGroupSessionEntityFields.ROOM_ID, String::class.java, FieldAttribute.PRIMARY_KEY)
+ .addField(CurrentGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, String::class.java)
+ .addField(CurrentGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java)
+ .addField(CurrentGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java)
+ .addField(CurrentGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java)
+ .addField(CurrentGroupSessionEntityFields.BACKED_UP, Boolean::class.java)
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo022.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo022.kt
new file mode 100644
index 0000000..877f85c
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo022.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 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.sdn.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.sdn.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
+import org.sdn.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
+import org.sdn.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * This migration adds new fields into SharedSessionEntity
+ */
+internal class MigrateCryptoTo022(realm: DynamicRealm) : RealmMigrator(realm, 22) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("SharedSessionEntity")
+ ?.addField(SharedSessionEntityFields.LAST_UPDATE, Long::class.java)
+ ?.setNullable(SharedSessionEntityFields.LAST_UPDATE, true)
+ ?.addField(SharedSessionEntityFields.DIRECT_SHARE, Boolean::class.java)
+ ?.setNullable(SharedSessionEntityFields.DIRECT_SHARE, true)
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
index 096a076..6d78176 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
@@ -50,7 +50,8 @@ internal open class CryptoMetadataEntity(
var xSignUserPrivateKey: String? = null,
var xSignSelfSignedPrivateKey: String? = null,
var keyBackupRecoveryKey: String? = null,
- var keyBackupRecoveryKeyVersion: String? = null
+ var keyBackupRecoveryKeyVersion: String? = null,
+ var fbk25519: String? = null
// var crossSigningInfoEntity: CrossSigningInfoEntity? = null
) : RealmObject() {
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CurrentGroupSessionEntity.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CurrentGroupSessionEntity.kt
new file mode 100644
index 0000000..e152740
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/CurrentGroupSessionEntity.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.store.db.model
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+import org.sdn.android.sdk.internal.crypto.model.InboundGroupSessionData
+import org.sdn.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
+import org.sdn.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.sdn.android.sdk.internal.crypto.store.db.serializeForRealm
+import org.sdn.android.sdk.internal.di.MoshiProvider
+import org.matrix.olm.OlmInboundGroupSession
+import timber.log.Timber
+
+internal open class CurrentGroupSessionEntity(
+
+ // denormalization for faster querying (these fields are in the inboundGroupSessionDataJson)
+ var sessionId: String? = null,
+ var senderKey: String? = null,
+ @PrimaryKey var roomId: String? = null,
+
+ // Deprecated, used for migration / olmInboundGroupSessionData contains Json
+ // keep it in case of problem to have a chance to recover
+ var olmInboundGroupSessionData: String? = null,
+
+ // Stores the session data in an extensible format
+ // to allow to store data not yet supported for later use
+ var inboundGroupSessionDataJson: String? = null,
+
+ // The pickled session
+ var serializedOlmInboundGroupSession: String? = null,
+
+ // Flag that indicates whether or not the current inboundSession will be shared to
+ // invited users to decrypt past messages
+ var sharedHistory: Boolean = false,
+ // Indicate if the key has been backed up to the homeserver
+ var backedUp: Boolean = false
+) :
+ RealmObject() {
+
+ fun store(wrapper: MXInboundMegolmSessionWrapper) {
+ this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session)
+ this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData)
+ this.roomId = wrapper.sessionData.roomId
+ this.senderKey = wrapper.sessionData.senderKey
+ this.sessionId = wrapper.session.sessionIdentifier()
+ this.sharedHistory = wrapper.sessionData.sharedHistory
+ }
+
+ private fun getOlmGroupSession(): OlmInboundGroupSession? {
+ return try {
+ deserializeFromRealm(serializedOlmInboundGroupSession)
+ } catch (failure: Throwable) {
+ Timber.e(failure, "## Deserialization failure")
+ return null
+ }
+ }
+
+ private fun getData(): InboundGroupSessionData? {
+ return try {
+ inboundGroupSessionDataJson?.let {
+ adapter.fromJson(it)
+ }
+ } catch (failure: Throwable) {
+ Timber.e(failure, "## Deserialization failure")
+ return null
+ }
+ }
+
+ fun toModel(): MXInboundMegolmSessionWrapper? {
+ val data = getData() ?: return null
+ val session = getOlmGroupSession() ?: return null
+ return MXInboundMegolmSessionWrapper(
+ session = session,
+ sessionData = data
+ )
+ }
+
+ companion object {
+ private val adapter = MoshiProvider.providesMoshi()
+ .adapter(InboundGroupSessionData::class.java)
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt
index 3f9eaf4..e7463e5 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt
@@ -31,6 +31,8 @@ internal open class SharedSessionEntity(
@Index var userId: String? = null,
@Index var deviceId: String? = null,
@Index var deviceIdentityKey: String? = null,
+ var lastUpdate: Long? = null,
+ var directShare: Boolean? = null,
var chainIndex: Int? = null
) : RealmObject() {
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt
index 1223d72..e4240ec 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt
@@ -20,7 +20,6 @@ import io.realm.Realm
import io.realm.RealmResults
import io.realm.kotlin.createObject
import io.realm.kotlin.where
-import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
@@ -35,7 +34,6 @@ internal fun SharedSessionEntity.Companion.get(
return realm.where()
.equalTo(SharedSessionEntityFields.ROOM_ID, roomId)
.equalTo(SharedSessionEntityFields.SESSION_ID, sessionId)
- .equalTo(SharedSessionEntityFields.ALGORITHM, MXCRYPTO_ALGORITHM_MEGOLM)
.equalTo(SharedSessionEntityFields.USER_ID, userId)
.equalTo(SharedSessionEntityFields.DEVICE_ID, deviceId)
.equalTo(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, deviceIdentityKey)
@@ -46,7 +44,6 @@ internal fun SharedSessionEntity.Companion.get(realm: Realm, roomId: String?, se
return realm.where()
.equalTo(SharedSessionEntityFields.ROOM_ID, roomId)
.equalTo(SharedSessionEntityFields.SESSION_ID, sessionId)
- .equalTo(SharedSessionEntityFields.ALGORITHM, MXCRYPTO_ALGORITHM_MEGOLM)
.findAll()
}
@@ -57,15 +54,18 @@ internal fun SharedSessionEntity.Companion.create(
userId: String,
deviceId: String,
deviceIdentityKey: String,
- chainIndex: Int
+ chainIndex: Int,
+ lastUpdate: Long?,
+ directShare: Boolean?
): SharedSessionEntity {
return realm.createObject().apply {
this.roomId = roomId
- this.algorithm = MXCRYPTO_ALGORITHM_MEGOLM
this.sessionId = sessionId
this.userId = userId
this.deviceId = deviceId
this.deviceIdentityKey = deviceIdentityKey
this.chainIndex = chainIndex
+ this.lastUpdate = lastUpdate
+ this.directShare = directShare
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt
index 61e7ff9..a703ea8 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt
@@ -19,7 +19,6 @@ package org.sdn.android.sdk.internal.crypto.store.db.query
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.where
-import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.sdn.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
import org.sdn.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
@@ -27,7 +26,6 @@ internal fun WithHeldSessionEntity.Companion.get(realm: Realm, roomId: String, s
return realm.where()
.equalTo(WithHeldSessionEntityFields.ROOM_ID, roomId)
.equalTo(WithHeldSessionEntityFields.SESSION_ID, sessionId)
- .equalTo(WithHeldSessionEntityFields.ALGORITHM, MXCRYPTO_ALGORITHM_MEGOLM)
.findFirst()
}
@@ -35,7 +33,6 @@ internal fun WithHeldSessionEntity.Companion.getOrCreate(realm: Realm, roomId: S
return get(realm, roomId, sessionId)
?: realm.createObject().apply {
this.roomId = roomId
- this.algorithm = MXCRYPTO_ALGORITHM_MEGOLM
this.sessionId = sessionId
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/EncryptEventTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
index 86f2360..e785e55 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
@@ -17,6 +17,7 @@ package org.sdn.android.sdk.internal.crypto.tasks
import dagger.Lazy
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.session.crypto.CryptoService
import org.sdn.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
import org.sdn.android.sdk.api.session.crypto.model.MXEventDecryptionResult
@@ -28,6 +29,7 @@ import org.sdn.android.sdk.api.util.awaitCallback
import org.sdn.android.sdk.internal.database.mapper.ContentMapper
import org.sdn.android.sdk.internal.session.room.send.LocalEchoRepository
import org.sdn.android.sdk.internal.task.Task
+import timber.log.Timber
import javax.inject.Inject
internal interface EncryptEventTask : Task {
@@ -46,6 +48,12 @@ internal class DefaultEncryptEventTask @Inject constructor(
override suspend fun execute(params: EncryptEventTask.Params): Event {
// don't want to wait for any query
// if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
+
+ val memberNum = cryptoService.get().getRoomUserIds(params.roomId).size
+ if (memberNum > 200) {
+ Timber.i("## EncryptEventTask: skip encrypting event for room ${params.roomId} with member count $memberNum")
+ return params.event
+ }
val localEvent = params.event
require(localEvent.eventId != null)
require(localEvent.type != null)
@@ -72,7 +80,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
val safeResult = result.copy(eventContent = modifiedContent)
// Better handling of local echo, to avoid decrypting transition on remote echo
// Should I only do it for text messages?
- val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
+ val decryptionLocalEcho = if (arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(result.eventContent["algorithm"])) {
MXEventDecryptionResult(
clearEvent = Event(
type = localEvent.type,
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/GetSessionMapTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/GetSessionMapTask.kt
new file mode 100644
index 0000000..e05fe89
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/GetSessionMapTask.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.tasks
+
+import org.sdn.android.sdk.internal.crypto.api.CryptoApi
+import org.sdn.android.sdk.internal.network.GlobalErrorReceiver
+import org.sdn.android.sdk.internal.network.executeRequest
+import org.sdn.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface GetSessionMapTask : Task>> {
+ data class Params(val roomId: String, val sessionId: String)
+}
+
+internal class DefaultGetSessionMapTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val globalErrorReceiver: GlobalErrorReceiver
+) : GetSessionMapTask {
+
+ override suspend fun execute(params: GetSessionMapTask.Params): Map> {
+ return executeRequest(globalErrorReceiver) {
+ cryptoApi.getSessionMap(params.roomId, params.sessionId)
+ }
+ }
+}
+
+internal interface PutSessionMapTask : Task {
+ data class Params(val roomId: String, val sessionId: String, val sessionMap: Map>)
+}
+
+internal class DefaultPutSessionMapTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val globalErrorReceiver: GlobalErrorReceiver
+) : PutSessionMapTask {
+
+ override suspend fun execute(params: PutSessionMapTask.Params) {
+ return executeRequest(globalErrorReceiver) {
+ cryptoApi.putSessionMap(params.roomId, params.sessionId, params.sessionMap)
+ }
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/PullSessionKeysTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/PullSessionKeysTask.kt
new file mode 100644
index 0000000..1e916f3
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/PullSessionKeysTask.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.tasks
+
+import org.sdn.android.sdk.internal.crypto.api.CryptoApi
+import org.sdn.android.sdk.internal.crypto.model.rest.PullKeysBody
+import org.sdn.android.sdk.internal.network.GlobalErrorReceiver
+import org.sdn.android.sdk.internal.network.executeRequest
+import org.sdn.android.sdk.internal.task.Task
+import org.sdn.android.sdk.api.session.sync.model.ToDeviceSyncResponse
+import timber.log.Timber
+import javax.inject.Inject
+
+internal interface PullSessionKeysTask : Task {
+ data class Params(
+ val sessionId: String
+ )
+}
+
+internal class DefaultPullSessionKeysTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val globalErrorReceiver: GlobalErrorReceiver
+) : PullSessionKeysTask {
+
+ override suspend fun execute(params: PullSessionKeysTask.Params): ToDeviceSyncResponse {
+ val body = PullKeysBody (
+ traceId = params.sessionId
+ )
+
+ Timber.i("## pulling session keys for -> ${body.traceId}")
+
+ return executeRequest(globalErrorReceiver) {
+ cryptoApi.pullRoomKeys(body)
+ }
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendEventTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendEventTask.kt
index 63e2558..8ff24c8 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -16,6 +16,7 @@
package org.sdn.android.sdk.internal.crypto.tasks
import org.sdn.android.sdk.api.session.events.model.Event
+import org.sdn.android.sdk.api.session.events.model.EventType
import org.sdn.android.sdk.api.session.room.model.localecho.RoomLocalEcho
import org.sdn.android.sdk.api.session.room.send.SendState
import org.sdn.android.sdk.internal.network.GlobalErrorReceiver
@@ -93,6 +94,9 @@ internal class DefaultSendEventTask @Inject constructor(
@Throws
private suspend fun handleEncryption(params: SendEventTask.Params): Event {
+ if (params.event.type == EventType.ROOM_KEY_REQUIRE || params.event.type == EventType.ROOM_KEY_REPLY) {
+ return params.event
+ }
if (params.encrypt && !params.event.isEncrypted()) {
return encryptEventTask.execute(
EncryptEventTask.Params(
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendSimpleEventTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendSimpleEventTask.kt
new file mode 100644
index 0000000..46cea16
--- /dev/null
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/tasks/SendSimpleEventTask.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.sdn.android.sdk.internal.crypto.tasks
+
+import org.sdn.android.sdk.api.session.events.model.Content
+import org.sdn.android.sdk.api.session.events.model.LocalEcho
+import org.sdn.android.sdk.internal.network.GlobalErrorReceiver
+import org.sdn.android.sdk.internal.network.executeRequest
+import org.sdn.android.sdk.internal.session.room.RoomAPI
+import org.sdn.android.sdk.internal.task.Task
+import timber.log.Timber
+import javax.inject.Inject
+
+internal interface SendSimpleEventTask : Task {
+ data class Params(
+ val roomId: String,
+ val eventType: String,
+ val content: Content
+ )
+}
+
+internal class DefaultSendSimpleEventTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val globalErrorReceiver: GlobalErrorReceiver
+) : SendSimpleEventTask {
+
+ override suspend fun execute(params: SendSimpleEventTask.Params): String {
+ try {
+ val localId = LocalEcho.createLocalEchoId()
+ val response = executeRequest(globalErrorReceiver) {
+ roomAPI.send(
+ localId,
+ roomId = params.roomId,
+ content = params.content,
+ eventType = params.eventType,
+ )
+ }
+ return response.eventId.also {
+ Timber.d("Event: $it just sent in ${params.roomId}")
+ }
+ } catch (e: Throwable) {
+ Timber.w(e, "Unable to send the Event")
+ throw e
+ }
+ }
+}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index 22ec3fc..19d153a 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
@@ -17,6 +17,7 @@
package org.sdn.android.sdk.internal.database.mapper
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.sdn.android.sdk.api.session.room.model.RoomEncryptionAlgorithm
import org.sdn.android.sdk.api.session.room.model.RoomJoinRules
@@ -75,7 +76,7 @@ internal class RoomSummaryMapper @Inject constructor(
isEncrypted = roomSummaryEntity.isEncrypted,
encryptionEventTs = roomSummaryEntity.encryptionEventTs,
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
- roomEncryptionTrustLevel = if (roomSummaryEntity.isEncrypted && roomSummaryEntity.e2eAlgorithm != MXCRYPTO_ALGORITHM_MEGOLM) {
+ roomEncryptionTrustLevel = if (roomSummaryEntity.isEncrypted && !arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(roomSummaryEntity.e2eAlgorithm)) {
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm
} else roomSummaryEntity.roomEncryptionTrustLevel,
inviterId = roomSummaryEntity.inviterId,
@@ -114,6 +115,7 @@ internal class RoomSummaryMapper @Inject constructor(
// I should probably use #hasEncryptorClassForAlgorithm but it says it supports
// OLM which is some legacy? Now only megolm allowed in rooms
MXCRYPTO_ALGORITHM_MEGOLM -> RoomEncryptionAlgorithm.Megolm
+ MXCRYPTO_ALGORITHM_RATCHET -> RoomEncryptionAlgorithm.Ratchet
else -> RoomEncryptionAlgorithm.UnsupportedAlgorithm(alg)
}
)
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/cleanup/CleanupSession.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/cleanup/CleanupSession.kt
index df86172..ffcfdb7 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/cleanup/CleanupSession.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/cleanup/CleanupSession.kt
@@ -59,7 +59,7 @@ internal class CleanupSession @Inject constructor(
sessionManager.stopSession(sessionId)
}
- suspend fun cleanup() {
+ suspend fun cleanup(deleteCrypto: Boolean = false) {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)")
@@ -72,20 +72,23 @@ internal class CleanupSession @Inject constructor(
Timber.d("Cleanup: clear session data...")
clearSessionDataTask.execute(Unit)
+ if (deleteCrypto) {
+ Timber.d("Cleanup: clear crypto data...")
+ clearCryptoDataTask.execute(Unit)
- Timber.d("Cleanup: clear crypto data...")
- clearCryptoDataTask.execute(Unit)
-
- Timber.d("Cleanup: clear the database keys")
- realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
- realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
+ Timber.d("Cleanup: clear the database keys")
+ realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
+ realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
+ }
// Wait for all the Realm instance to be released properly. Closing Realm instance is async.
// After that we can safely delete the Realm files
waitRealmRelease()
Timber.d("Cleanup: clear file system")
- sessionFiles.deleteRecursively()
+ if (deleteCrypto) {
+ sessionFiles.deleteRecursively()
+ }
sessionCache.deleteRecursively()
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
index 071e7d8..3f8d824 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
@@ -17,6 +17,7 @@
package org.sdn.android.sdk.internal.session.room.create
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.extensions.tryOrNull
import org.sdn.android.sdk.api.session.events.model.Event
import org.sdn.android.sdk.api.session.events.model.EventType
@@ -176,7 +177,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
}
return params.algorithm
?.let {
- if (it != MXCRYPTO_ALGORITHM_MEGOLM) {
+ if (!arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(it)) {
throw InvalidParameterException("Unsupported algorithm: $it")
}
Event(
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
index ca2849a..5b69a0d 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
@@ -20,6 +20,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.session.crypto.CryptoService
import org.sdn.android.sdk.api.session.events.model.EventType
import org.sdn.android.sdk.api.session.room.crypto.RoomCryptoService
@@ -57,12 +58,13 @@ internal class DefaultRoomCryptoService @AssistedInject constructor(
}
override suspend fun enableEncryption(algorithm: String, force: Boolean) {
+ val supportedAlgorithm = arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET)
when {
- (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
+ (!force && isEncrypted() && supportedAlgorithm.contains(encryptionAlgorithm())) -> {
throw IllegalStateException("Encryption is already enabled for this room")
}
- (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> {
- throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
+ (!force && !supportedAlgorithm.contains(algorithm)) -> {
+ throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM or MXCRYPTO_ALGORITHM_RATCHET algorithm is supported")
}
else -> {
val params = SendStateTask.Params(
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/DefaultSignOutService.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/DefaultSignOutService.kt
index caa0b94..9e4d1e7 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/DefaultSignOutService.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/DefaultSignOutService.kt
@@ -35,7 +35,7 @@ internal class DefaultSignOutService @Inject constructor(
sessionParamsStore.updateCredentials(credentials)
}
- override suspend fun signOut(signOutFromHomeserver: Boolean) {
- return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver))
+ override suspend fun signOut(signOutFromHomeserver: Boolean, deleteCrypto: Boolean) {
+ return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver, deleteCrypto))
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/SignOutTask.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/SignOutTask.kt
index 029361d..66975a5 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/SignOutTask.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/signout/SignOutTask.kt
@@ -30,7 +30,8 @@ import javax.inject.Inject
internal interface SignOutTask : Task {
data class Params(
- val signOutFromHomeserver: Boolean
+ val signOutFromHomeserver: Boolean,
+ val deleteCrypto: Boolean
)
}
@@ -75,6 +76,6 @@ internal class DefaultSignOutTask @Inject constructor(
}
Timber.d("SignOut: cleanup session...")
- cleanupSession.cleanup()
+ cleanupSession.cleanup(params.deleteCrypto)
}
}
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index 32eeddd..de3e4aa 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -95,7 +95,8 @@ internal class CryptoSyncHandler @Inject constructor(
*/
private suspend fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
Timber.v("## CRYPTO | decryptToDeviceEvent")
- if (event.getClearType() == EventType.ENCRYPTED) {
+ val eventType = event.getClearType()
+ if (eventType == EventType.ENCRYPTED || eventType == EventType.ROOM_KEY_REPLY) {
var result: MXEventDecryptionResult? = null
try {
result = cryptoService.decryptEvent(event, timelineId ?: "")
diff --git a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 2a190ab..5cb2bad 100644
--- a/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -21,6 +21,7 @@ import io.realm.Realm
import io.realm.kotlin.createObject
import kotlinx.coroutines.runBlocking
import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
import org.sdn.android.sdk.api.session.crypto.MXCryptoError
import org.sdn.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.sdn.android.sdk.api.session.events.model.Event
@@ -494,7 +495,7 @@ internal class RoomSyncHandler @Inject constructor(
if (sendingEventEntity != null) {
Timber.v("Remove local echo for tx:$txId")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
- if (event.isEncrypted() && event.content?.get("algorithm") as? String == MXCRYPTO_ALGORITHM_MEGOLM) {
+ if (event.isEncrypted() && arrayOf(MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_RATCHET).contains(event.content?.get("algorithm") as? String)) {
// updated with echo decryption, to avoid seeing txId decrypt again
val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java)
sendingEventEntity.root?.decryptionResultJson?.let { json ->
diff --git a/sdn-sdk-radix/build.gradle b/sdn-sdk-radix/build.gradle
index 01635c1..a18b9e0 100644
--- a/sdn-sdk-radix/build.gradle
+++ b/sdn-sdk-radix/build.gradle
@@ -53,7 +53,7 @@ android {
ext {
PUBLISH_GROUP_ID = 'io.github.linx-opennetwork'
- PUBLISH_VERSION = '0.1.73'
+ PUBLISH_VERSION = '0.1.74'
PUBLISH_ARTIFACT_ID = 'sdn-sdk-radix'
}
diff --git a/sdn-sdk-service/build.gradle b/sdn-sdk-service/build.gradle
index fbff1ae..41bed58 100644
--- a/sdn-sdk-service/build.gradle
+++ b/sdn-sdk-service/build.gradle
@@ -39,7 +39,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- implementation 'io.github.linx-opennetwork:sdn-sdk-radix:0.1.73'
+ implementation 'io.github.linx-opennetwork:sdn-sdk-radix:0.2.4'
// implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}