diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 20a03d0eee..160f25fa58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -18,12 +18,14 @@ import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.sms.IncomingGroupMessage @@ -41,6 +43,10 @@ class Storage(val context: Context): StorageProtocol { return Pair(userPublicKey, userPrivateKey) } + override fun getUserX25519KeyPair(): ECKeyPair { + return DatabaseFactory.getLokiAPIDatabase(context).getUserX25519KeyPair() + } + override fun getUserDisplayName(): String? { return TextSecurePreferences.getProfileName(context) } @@ -258,6 +264,14 @@ class Storage(val context: Context): StorageProtocol { mmsDB.markAsSent(infoMessageID, true) } + override fun isClosedGroup(publicKey: String): Boolean { + TODO("Not yet implemented") + } + + override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList { + TODO("Not yet implemented") + } + override fun setProfileSharing(address: Address, value: Boolean) { val recipient = Recipient.from(context, address, false) DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, value) diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index 6de13761cf..52c1cbf090 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -27,6 +27,7 @@ interface StorageProtocol { // General fun getUserPublicKey(): String? fun getUserKeyPair(): Pair? + fun getUserX25519KeyPair(): ECKeyPair fun getUserDisplayName(): String? fun getUserProfileKey(): ByteArray? fun getUserProfilePictureURL(): String? @@ -101,6 +102,8 @@ interface StorageProtocol { name: String, members: Collection, admins: Collection) fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, members: Collection, admins: Collection, threadID: Long) + fun isClosedGroup(publicKey: String): Boolean //TODO + fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList //TODO // Settings fun setProfileSharing(address: Address, value: Boolean) 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 031af57db2..e19686fabc 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 @@ -7,8 +7,10 @@ import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ReadReceipt import org.session.libsession.messaging.messages.control.TypingIndicator import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.service.internal.push.SignalServiceProtos +import java.lang.Error object MessageReceiver { internal sealed class Error(val description: String) : Exception() { @@ -16,7 +18,8 @@ object MessageReceiver { object InvalidMessage: Error("Invalid message.") object UnknownMessage: Error("Unknown message type.") object UnknownEnvelopeType: Error("Unknown envelope type.") - object NoUserPublicKey: Error("Couldn't find user key pair.") + object NoUserX25519KeyPair: Error("Couldn't find user X25519 key pair.") + object NoUserED25519KeyPair: Error("Couldn't find user ED25519 key pair.") object NoData: Error("Received an empty envelope.") object SenderBlocked: Error("Received a message from a blocked user.") object NoThread: Error("Couldn't find thread for message.") @@ -24,7 +27,7 @@ object MessageReceiver { object ParsingFailed : Error("Couldn't parse ciphertext message.") // Shared sender keys object InvalidGroupPublicKey: Error("Invalid group public key.") - object NoGroupPrivateKey: Error("Missing group private key.") + object NoGroupKeyPair: Error("Missing group key pair.") object SharedSecretGenerationFailed: Error("Couldn't generate a shared secret.") internal val isRetryable: Boolean = when (this) { @@ -47,8 +50,9 @@ object MessageReceiver { if (storage.getReceivedMessageTimestamps().contains(envelope.timestamp)) throw Error.DuplicateMessage storage.addReceivedMessageTimestamp(envelope.timestamp) // Decrypt the contents - val plaintext: ByteArray - val sender: String + val ciphertext = envelope.content ?: throw Error.NoData + var plaintext: ByteArray? = null + var sender: String? = null var groupPublicKey: String? = null if (isOpenGroupMessage) { plaintext = envelope.content.toByteArray() @@ -56,20 +60,43 @@ object MessageReceiver { } else { when (envelope.type) { SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER -> { - val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(envelope) + val userX25519KeyPair = MessagingConfiguration.shared.storage.getUserX25519KeyPair() ?: throw Error.NoUserX25519KeyPair + val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), userX25519KeyPair) plaintext = decryptionResult.first sender = decryptionResult.second } SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT -> { - val decryptionResult = MessageReceiverDecryption.decryptWithSharedSenderKeys(envelope) - plaintext = decryptionResult.first - sender = decryptionResult.second + val hexEncodedGroupPublicKey = envelope.source + if (hexEncodedGroupPublicKey == null || MessagingConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey)) { + throw Error.InvalidGroupPublicKey + } + val encryptionKeyPairs = MessagingConfiguration.shared.storage.getClosedGroupEncryptionKeyPairs(hexEncodedGroupPublicKey) + if (encryptionKeyPairs.isEmpty()) { throw Error.NoGroupKeyPair } + // Loop through all known group key pairs in reverse order (i.e. try the latest key pair first (which'll more than + // likely be the one we want) but try older ones in case that didn't work) + var encryptionKeyPair = encryptionKeyPairs.removeLast() + fun decrypt() { + try { + val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), encryptionKeyPair) + plaintext = decryptionResult.first + sender = decryptionResult.second + } catch (e: Exception) { + if (encryptionKeyPairs.isNotEmpty()) { + encryptionKeyPair = encryptionKeyPairs.removeLast() + decrypt() + } else { + throw e + } + } + } + decrypt() + groupPublicKey = envelope.source } else -> throw Error.UnknownEnvelopeType } } // Don't process the envelope any further if the sender is blocked - if (isBlock(sender)) throw Error.SenderBlocked + if (isBlock(sender!!)) throw Error.SenderBlocked // Ignore self sends if (sender == userPublicKey) throw Error.SelfSend // Parse the proto diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverDecryption.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverDecryption.kt index 9ed6870f99..cb6320a717 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverDecryption.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverDecryption.kt @@ -4,6 +4,7 @@ import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error import org.session.libsession.utilities.AESGCM import org.session.libsession.utilities.GroupUtil +import org.session.libsignal.libsignal.ecc.ECKeyPair import org.whispersystems.curve25519.Curve25519 @@ -32,11 +33,11 @@ object MessageReceiverDecryption { return Pair(ByteArray(1), result.sender) // TODO: Return real plaintext }*/ - internal fun decryptWithSessionProtocol(envelope: SignalServiceProtos.Envelope): Pair { - return MessagingConfiguration.shared.sessionProtocol.decrypt(SignalServiceEnvelope(envelope)) + internal fun decryptWithSessionProtocol(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair { + return MessagingConfiguration.shared.sessionProtocol.decrypt(ciphertext, x25519KeyPair) } - internal fun decryptWithSharedSenderKeys(envelope: SignalServiceProtos.Envelope): Pair { + /*internal fun decryptWithSharedSenderKeys(envelope: SignalServiceProtos.Envelope): Pair { // 1. ) Check preconditions val groupPublicKey = envelope.source if (!GroupUtil.isClosedGroup(groupPublicKey)) { throw Error.InvalidGroupPublicKey } @@ -61,5 +62,5 @@ object MessageReceiverDecryption { val plaintext = SharedSenderKeysImplementation.shared.decrypt(closedGroupCiphertextMessage.ivAndCiphertext, groupPublicKey, senderPublicKey, closedGroupCiphertextMessage.keyIndex) // 6. ) Return return Pair(plaintext, senderPublicKey) - } + }*/ } \ No newline at end of file