diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 5d181e5207..745e9067f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -191,7 +191,7 @@ object ClosedGroupsProtocolV2 { } if (userPublicKey in admins) { // send current encryption key to the latest added members - val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull() + val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull() ?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) if (encryptionKeyPair == null) { Log.d("Loki", "Couldn't get encryption key pair for closed group.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt index e7b09bc623..a3c81c9390 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt @@ -15,6 +15,14 @@ object MessageDecrypter { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } + /** + * Decrypts `ciphertext` using the Session protocol and `x25519KeyPair`. + * + * @param ciphertext the data to decrypt. + * @param x25519KeyPair the key pair to use for decryption. This could be the current user's key pair, or the key pair of a closed group. + * + * @return the padded plaintext. + */ public fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair { val recipientX25519PrivateKey = x25519KeyPair.privateKey.serialize() val recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded()) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderEncryption.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt similarity index 93% rename from libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderEncryption.kt rename to libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt index c96fc82b16..287fa0cef4 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderEncryption.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt @@ -13,7 +13,7 @@ import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.logging.Log -object MessageSenderEncryption { +object MessageEncrypter { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } @@ -25,7 +25,7 @@ object MessageSenderEncryption { * * @return the encrypted message. */ - internal fun encryptWithSessionProtocol(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray{ + internal fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray{ val context = MessagingModuleConfiguration.shared.context val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw Error.NoUserED25519KeyPair val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded()) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 28f73521f2..6335bd92f8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -21,7 +21,6 @@ object MessageReceiver { object SenderBlocked: Error("Received a message from a blocked user.") object NoThread: Error("Couldn't find thread for message.") object SelfSend: Error("Message addressed at self.") - // Shared sender keys object InvalidGroupPublicKey: Error("Invalid group public key.") object NoGroupKeyPair: Error("Missing group key pair.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index df7a0d7df8..4477633c16 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -14,7 +14,6 @@ import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.visible.* import org.session.libsession.messaging.open_groups.* import org.session.libsession.messaging.threads.Address -import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.SnodeAPI @@ -27,6 +26,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.logging.Log +import java.lang.IllegalStateException import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote @@ -37,8 +37,6 @@ object MessageSender { sealed class Error(val description: String) : Exception(description) { object InvalidMessage : Error("Invalid message.") object ProtoConversionFailed : Error("Couldn't convert message to proto.") - object ProofOfWorkCalculationFailed : Error("Proof of work calculation failed.") - object NoUserX25519KeyPair : Error("Couldn't find user X25519 key pair.") object NoUserED25519KeyPair : Error("Couldn't find user ED25519 key pair.") object SigningFailed : Error("Couldn't sign message.") object EncryptionFailed : Error("Couldn't encrypt message.") @@ -46,17 +44,10 @@ object MessageSender { // Closed groups object NoThread : Error("Couldn't find a thread associated with the given group public key.") object NoKeyPair: Error("Couldn't find a private key associated with the given group public key.") - object NoPrivateKey : Error("Couldn't find a private key associated with the given group public key.") object InvalidClosedGroupUpdate : Error("Invalid group update.") - // Precondition - class PreconditionFailure(val reason: String): Error(reason) - internal val isRetryable: Boolean = when (this) { - is InvalidMessage -> false - is ProtoConversionFailed -> false - is ProofOfWorkCalculationFailed -> false - is InvalidClosedGroupUpdate -> false + is InvalidMessage, ProtoConversionFailed, InvalidClosedGroupUpdate -> false else -> true } } @@ -76,7 +67,9 @@ object MessageSender { val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey() // Set the timestamp, sender and recipient - message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */ + if (message.sentTimestamp == null) { + message.sentTimestamp = System.currentTimeMillis() // Visible messages will already have their sent timestamp set + } message.sender = userPublicKey val isSelfSend = (message.recipient == userPublicKey) // Set the failure handler (need it here already for precondition failure handling) @@ -91,8 +84,7 @@ object MessageSender { when (destination) { is Destination.Contact -> message.recipient = destination.publicKey is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey - is Destination.OpenGroup, - is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!") + is Destination.OpenGroup, is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be an open group.") } // Validate the message if (!message.isValid()) { throw Error.InvalidMessage } @@ -125,13 +117,12 @@ object MessageSender { // Encrypt the serialized protobuf val ciphertext: ByteArray when (destination) { - is Destination.Contact -> ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, destination.publicKey) + is Destination.Contact -> ciphertext = MessageEncrypter.encrypt(plaintext, destination.publicKey) is Destination.ClosedGroup -> { val encryptionKeyPair = MessagingModuleConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!! - ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey) + ciphertext = MessageEncrypter.encrypt(plaintext, encryptionKeyPair.hexEncodedPublicKey) } - is Destination.OpenGroup, - is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!") + is Destination.OpenGroup, is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.") } // Wrap the result val kind: SignalServiceProtos.Envelope.Type @@ -145,8 +136,7 @@ object MessageSender { kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_MESSAGE senderPublicKey = destination.groupPublicKey } - is Destination.OpenGroup, - is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!") + is Destination.OpenGroup, is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.") } val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext) // Send the result @@ -201,7 +191,9 @@ object MessageSender { private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise { val deferred = deferred() val storage = MessagingModuleConfiguration.shared.storage - message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } + if (message.sentTimestamp == null) { + message.sentTimestamp = System.currentTimeMillis() + } message.sender = storage.getUserPublicKey() // Set the failure handler (need it here already for precondition failure handling) fun handleFailure(error: Exception) { @@ -210,18 +202,15 @@ object MessageSender { } try { when (destination) { - is Destination.Contact -> throw Error.PreconditionFailure("Destination should not be contacts!") - is Destination.ClosedGroup -> throw Error.PreconditionFailure("Destination should not be closed groups!") + is Destination.Contact, is Destination.ClosedGroup -> throw IllegalStateException("Invalid destination.") is Destination.OpenGroup -> { message.recipient = "${destination.server}.${destination.channel}" val server = destination.server val channel = destination.channel - // Validate the message if (message !is VisibleMessage || !message.isValid()) { throw Error.InvalidMessage } - // Convert the message to an open group message val openGroupMessage = OpenGroupMessage.from(message, server) ?: run { throw Error.InvalidMessage @@ -239,7 +228,6 @@ object MessageSender { message.recipient = "${destination.server}.${destination.room}" val server = destination.server val room = destination.room - // Attach the user's profile if needed if (message is VisibleMessage) { val displayName = storage.getUserDisplayName()!! @@ -251,20 +239,17 @@ object MessageSender { message.profile = Profile(displayName) } } - // Validate the message if (message !is VisibleMessage || !message.isValid()) { throw Error.InvalidMessage } - val proto = message.toProto()!! val plaintext = PushTransportDetails.getPaddedMessageBody(proto.toByteArray()) val openGroupMessage = OpenGroupMessageV2( - sender = message.sender, - sentTimestamp = message.sentTimestamp!!, - base64EncodedData = Base64.encodeBytes(plaintext), + sender = message.sender, + sentTimestamp = message.sentTimestamp!!, + base64EncodedData = Base64.encodeBytes(plaintext), ) - OpenGroupAPIV2.send(openGroupMessage,room,server).success { message.openGroupServerMessageID = it.serverID handleSuccessfulMessageSend(message, destination) @@ -272,7 +257,6 @@ object MessageSender { }.fail { handleFailure(it) } - } } } catch (exception: Exception) { @@ -285,7 +269,7 @@ object MessageSender { fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false) { val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! - val messageId = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return + val messageID = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return // Ignore future self-sends storage.addReceivedMessageTimestamp(message.sentTimestamp!!) // Track the open group server message ID @@ -293,7 +277,7 @@ object MessageSender { val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray()) val threadID = storage.getThreadIdFor(Address.fromSerialized(encoded)) if (threadID != null && threadID >= 0) { - storage.setOpenGroupServerMessageID(messageId, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) + storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) } } // Mark the message as sent @@ -323,16 +307,16 @@ object MessageSender { // Convenience @JvmStatic fun send(message: VisibleMessage, address: Address, attachments: List, quote: SignalQuote?, linkPreview: SignalLinkPreview?) { - val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider - val attachmentIDs = dataProvider.getAttachmentIDsFor(message.id!!) + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + val attachmentIDs = messageDataProvider.getAttachmentIDsFor(message.id!!) message.attachmentIDs.addAll(attachmentIDs) message.quote = Quote.from(quote) message.linkPreview = LinkPreview.from(linkPreview) - message.linkPreview?.let { - if (it.attachmentID == null) { - dataProvider.getLinkPreviewAttachmentIDFor(message.id!!)?.let { - message.linkPreview!!.attachmentID = it - message.attachmentIDs.remove(it) + message.linkPreview?.let { linkPreview -> + if (linkPreview.attachmentID == null) { + messageDataProvider.getLinkPreviewAttachmentIDFor(message.id!!)?.let { attachmentID -> + message.linkPreview!!.attachmentID = attachmentID + message.attachmentIDs.remove(attachmentID) } } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index a7c6dd97f7..f42127762c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -26,7 +26,8 @@ import java.util.* import java.util.concurrent.ConcurrentHashMap const val groupSizeLimit = 100 -val pendingKeyPair = ConcurrentHashMap>() + +val pendingKeyPairs = ConcurrentHashMap>() fun MessageSender.create(name: String, members: Collection): Promise { val deferred = deferred() @@ -45,7 +46,7 @@ fun MessageSender.create(name: String, members: Collection): Promise - val ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, publicKey) + val ciphertext = MessageEncrypter.encrypt(plaintext, publicKey) ClosedGroupControlMessage.KeyPairWrapper(publicKey, ByteString.copyFrom(ciphertext)) } val kind = ClosedGroupControlMessage.Kind.EncryptionKeyPair(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), wrappers) @@ -307,14 +303,14 @@ fun MessageSender.sendLatestEncryptionKeyPair(publicKey: String, groupPublicKey: return } // Get the latest encryption key pair - val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull() - ?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return + val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull() + ?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return // Send it val proto = SignalServiceProtos.KeyPair.newBuilder() proto.publicKey = ByteString.copyFrom(encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) proto.privateKey = ByteString.copyFrom(encryptionKeyPair.privateKey.serialize()) val plaintext = proto.build().toByteArray() - val ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, publicKey) + val ciphertext = MessageEncrypter.encrypt(plaintext, publicKey) Log.d("Loki", "Sending latest encryption key pair to: $publicKey.") val wrapper = ClosedGroupControlMessage.KeyPairWrapper(publicKey, ByteString.copyFrom(ciphertext)) val kind = ClosedGroupControlMessage.Kind.EncryptionKeyPair(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), listOf(wrapper)) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index ea5cac4c77..6059ca44cd 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -401,7 +401,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo // // Without the code below, the added member(s) would never get the key pair that was generated by the admin when they saw // the member removed message. - val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull() + val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull() ?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) if (encryptionKeyPair == null) { android.util.Log.d("Loki", "Couldn't get encryption key pair for closed group.")