This commit is contained in:
andrew
2023-07-25 14:49:41 +09:30
parent 01e9d15872
commit 34990b13d3
11 changed files with 202 additions and 82 deletions

View File

@@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.notifications
import android.content.Context
import org.session.libsession.utilities.TextSecurePreferences
class ExpiryManager(
private val context: Context,
private val interval: Int = 12 * 60 * 60 * 1000
) {
fun isExpired() = currentTime() > time + interval
fun markTime() {
time = currentTime()
}
fun clearTime() {
time = 0
}
private var time
get() = TextSecurePreferences.getLastFCMUploadTime(context)
set(value) = TextSecurePreferences.setLastFCMUploadTime(context, value)
private fun currentTime() = System.currentTimeMillis()
}

View File

@@ -1,26 +0,0 @@
package org.thoughtcrime.securesms.notifications
import android.content.Context
import org.session.libsession.utilities.TextSecurePreferences
class FcmTokenManager(
private val context: Context,
private val expiryManager: ExpiryManager
) {
val isUsingFCM get() = TextSecurePreferences.isUsingFCM(context)
var fcmToken
get() = TextSecurePreferences.getFCMToken(context)
set(value) {
TextSecurePreferences.setFCMToken(context, value)
if (value != null) markTime() else clearTime()
}
val requiresUnregister get() = fcmToken != null
private fun clearTime() = expiryManager.clearTime()
private fun markTime() = expiryManager.markTime()
private fun isExpired() = expiryManager.isExpired()
fun isInvalid(): Boolean = fcmToken == null || isExpired()
}

View File

@@ -40,6 +40,7 @@ import org.session.libsignal.utilities.emptyPromise
import org.session.libsignal.utilities.retryIfNeeded
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import javax.inject.Inject
private const val TAG = "FirebasePushManager"
@@ -47,7 +48,7 @@ class FirebasePushManager(
private val context: Context
): PushManager {
private val pushManagerV2 = PushManagerV2(context)
@Inject lateinit var pushManagerV2: PushManagerV2
companion object {
const val maxRetryCount = 4
@@ -132,6 +133,4 @@ class FirebasePushManager(
} success {
tokenManager.fcmToken = null
}
fun decrypt(decode: ByteArray) = pushManagerV2.decrypt(decode)
}

View File

@@ -39,7 +39,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
private const val TAG = "PushManagerV2"
class PushManagerV2(private val context: Context) {
class PushManagerV2(private val pushHandler: PushHandler) {
private val sodium = LazySodiumAndroid(SodiumAndroid())
fun register(
@@ -48,7 +48,7 @@ class PushManagerV2(private val context: Context) {
userEd25519Key: KeyPair,
namespaces: List<Int>
): Promise<SubscriptionResponse, Exception> {
val pnKey = getOrCreateNotificationKey()
val pnKey = pushHandler.getOrCreateNotificationKey()
val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s
// if we want to support passing namespace list, here is the place to do it
@@ -117,41 +117,4 @@ class PushManagerV2(private val context: Context) {
.also { if (it.isFailure()) throw Exception("error: ${it.message}.") }
}
}
private 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)
}
return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY))
}
fun decrypt(encPayload: ByteArray): ByteArray? {
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))
val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null
// null content is valid only if we got a "data_too_long" flag
if (content == null)
check(metadata.data_too_long) { "missing message data, but no too-long flag" }
else
check(metadata.data_len == content.size) { "wrong message data size" }
Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B")
return content
}
}

View File

@@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint
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.PushManagerV1
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Base64
@@ -21,6 +20,7 @@ private const val TAG = "PushNotificationService"
class PushNotificationService : FirebaseMessagingService() {
@Inject lateinit var pushManager: FirebasePushManager
@Inject lateinit var pushHandler: PushHandler
override fun onNewToken(token: String) {
super.onNewToken(token)
@@ -32,37 +32,7 @@ class PushNotificationService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
Log.d(TAG, "Received a push notification.")
val data: ByteArray? = if (message.data.containsKey("spns")) {
// this is a v2 push notification
try {
pushManager.decrypt(Base64.decode(message.data["enc_payload"]))
} catch(e: Exception) {
Log.e(TAG, "Invalid push notification: ${e.message}")
return
}
} else {
// old v1 push notification; we still need this for receiving legacy closed group notifications
message.data?.get("ENCRYPTED_DATA")?.let(Base64::decode)
}
if (data != null) {
try {
val envelopeAsData = MessageWrapper.unwrap(data).toByteArray()
val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null)
JobQueue.shared.add(job)
} catch (e: Exception) {
Log.d(TAG, "Failed to unwrap data for message due to error: $e.")
}
} else {
Log.d(TAG, "Failed to decode data for message.")
val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER)
.setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
.setColor(resources.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(this).notify(11111, builder.build())
}
pushHandler.onPush(message.data)
}
override fun onDeletedMessages() {