2023-07-25 07:19:41 +02:00
|
|
|
package org.thoughtcrime.securesms.notifications
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
import androidx.core.app.NotificationCompat
|
|
|
|
import androidx.core.app.NotificationManagerCompat
|
|
|
|
import com.goterl.lazysodium.LazySodiumAndroid
|
|
|
|
import com.goterl.lazysodium.SodiumAndroid
|
|
|
|
import com.goterl.lazysodium.interfaces.AEAD
|
|
|
|
import com.goterl.lazysodium.utils.Key
|
|
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
|
|
import kotlinx.serialization.decodeFromString
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
|
|
|
import org.session.libsession.messaging.jobs.JobQueue
|
|
|
|
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
|
|
|
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata
|
|
|
|
import org.session.libsession.messaging.utilities.MessageWrapper
|
|
|
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
|
|
|
import org.session.libsession.utilities.bencode.Bencode
|
|
|
|
import org.session.libsession.utilities.bencode.BencodeList
|
|
|
|
import org.session.libsession.utilities.bencode.BencodeString
|
|
|
|
import org.session.libsignal.utilities.Base64
|
|
|
|
import org.session.libsignal.utilities.Log
|
|
|
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
private const val TAG = "PushHandler"
|
|
|
|
|
2023-08-06 14:52:39 +02:00
|
|
|
class PushReceiver @Inject constructor(@ApplicationContext val context: Context) {
|
2023-07-25 07:19:41 +02:00
|
|
|
private val sodium = LazySodiumAndroid(SodiumAndroid())
|
|
|
|
|
2023-07-26 03:18:20 +02:00
|
|
|
fun onPush(dataMap: Map<String, String>?) {
|
|
|
|
onPush(dataMap?.asByteArray())
|
|
|
|
}
|
|
|
|
|
2023-08-04 06:06:09 +02:00
|
|
|
fun onPush(data: ByteArray?) {
|
2023-07-26 03:18:20 +02:00
|
|
|
if (data == null) {
|
|
|
|
onPush()
|
|
|
|
return
|
2023-07-25 07:19:41 +02:00
|
|
|
}
|
|
|
|
|
2023-07-26 03:18:20 +02:00
|
|
|
try {
|
|
|
|
val envelopeAsData = MessageWrapper.unwrap(data).toByteArray()
|
|
|
|
val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null)
|
|
|
|
JobQueue.shared.add(job)
|
|
|
|
} catch (e: Exception) {
|
2023-08-11 10:07:35 +02:00
|
|
|
Log.d(TAG, "Failed to unwrap data for message due to error.", e)
|
2023-07-26 03:18:20 +02:00
|
|
|
}
|
2023-07-25 07:19:41 +02:00
|
|
|
}
|
|
|
|
|
2023-07-26 03:18:20 +02:00
|
|
|
private fun onPush() {
|
2023-07-25 07:19:41 +02:00
|
|
|
Log.d(TAG, "Failed to decode data for message.")
|
|
|
|
val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER)
|
|
|
|
.setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
|
|
|
|
.setColor(context.getColor(network.loki.messenger.R.color.textsecure_primary))
|
|
|
|
.setContentTitle("Session")
|
|
|
|
.setContentText("You've got a new message.")
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
|
|
.setAutoCancel(true)
|
|
|
|
NotificationManagerCompat.from(context).notify(11111, builder.build())
|
|
|
|
}
|
|
|
|
|
2023-07-26 03:18:20 +02:00
|
|
|
private fun Map<String, String>.asByteArray() =
|
|
|
|
when {
|
|
|
|
// this is a v2 push notification
|
|
|
|
containsKey("spns") -> {
|
|
|
|
try {
|
|
|
|
decrypt(Base64.decode(this["enc_payload"]))
|
|
|
|
} catch (e: Exception) {
|
2023-08-11 10:07:35 +02:00
|
|
|
Log.e(TAG, "Invalid push notification", e)
|
2023-07-26 03:18:20 +02:00
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// old v1 push notification; we still need this for receiving legacy closed group notifications
|
|
|
|
else -> this["ENCRYPTED_DATA"]?.let(Base64::decode)
|
2023-07-25 07:19:41 +02:00
|
|
|
}
|
|
|
|
|
2023-08-09 02:38:42 +02:00
|
|
|
private fun decrypt(encPayload: ByteArray): ByteArray? {
|
2023-07-25 07:19:41 +02:00
|
|
|
Log.d(TAG, "decrypt() called")
|
|
|
|
|
|
|
|
val encKey = getOrCreateNotificationKey()
|
|
|
|
val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
|
|
|
|
val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
|
|
|
|
val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce)
|
|
|
|
?: error("Failed to decrypt push notification")
|
|
|
|
val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray()
|
|
|
|
val bencoded = Bencode.Decoder(decrypted)
|
|
|
|
val expectedList = (bencoded.decode() as? BencodeList)?.values
|
|
|
|
?: error("Failed to decode bencoded list from payload")
|
|
|
|
|
|
|
|
val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata")
|
|
|
|
val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson))
|
|
|
|
|
2023-08-11 10:07:35 +02:00
|
|
|
return (expectedList.getOrNull(1) as? BencodeString)?.value.also {
|
|
|
|
// null content is valid only if we got a "data_too_long" flag
|
|
|
|
it?.let { check(metadata.data_len == it.size) { "wrong message data size" } }
|
|
|
|
?: check(metadata.data_too_long) { "missing message data, but no too-long flag" }
|
|
|
|
}
|
2023-07-25 07:19:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fun getOrCreateNotificationKey(): Key {
|
|
|
|
if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) {
|
|
|
|
// generate the key and store it
|
|
|
|
val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF)
|
|
|
|
IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString)
|
|
|
|
}
|
2023-07-26 03:18:20 +02:00
|
|
|
return Key.fromHexString(
|
|
|
|
IdentityKeyUtil.retrieve(
|
|
|
|
context,
|
|
|
|
IdentityKeyUtil.NOTIFICATION_KEY
|
|
|
|
)
|
|
|
|
)
|
2023-07-25 07:19:41 +02:00
|
|
|
}
|
|
|
|
}
|