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 2a07a59a77..2f41a71468 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,12 @@ 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.session.libsignal.service.loki.api.opengroups.PublicChat import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase @@ -46,6 +46,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return Pair(userPublicKey, userPrivateKey) } + override fun getUserX25519KeyPair(): ECKeyPair { + return DatabaseFactory.getLokiAPIDatabase(context).getUserX25519KeyPair() + } + override fun getUserDisplayName(): String? { return TextSecurePreferences.getProfileName(context) } @@ -270,6 +274,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, 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 getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair { + 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..998a84390c 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,9 @@ 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 + fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair //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 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 47c564be13..19af7d570b 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 @@ -23,6 +23,7 @@ import org.session.libsignal.service.api.messages.SignalServiceAttachment import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.util.Base64 import org.session.libsignal.service.loki.api.crypto.ProofOfWork +import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey object MessageSender { @@ -32,7 +33,10 @@ object MessageSender { object InvalidMessage : Error("Invalid message.") object ProtoConversionFailed : Error("Couldn't convert message to proto.") object ProofOfWorkCalculationFailed : Error("Proof of work calculation failed.") - 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 SigningFailed : Error("Couldn't sign message.") + object EncryptionFailed : Error("Couldn't encrypt message.") // Closed groups object NoThread : Error("Couldn't find a thread associated with the given group public key.") @@ -71,7 +75,7 @@ object MessageSender { var snodeMessage: SnodeMessage? = null // Set the timestamp, sender and recipient message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */ - message.sender = storage.getUserPublicKey() + message.sender = userPublicKey try { when (destination) { is Destination.Contact -> message.recipient = destination.publicKey @@ -117,7 +121,10 @@ object MessageSender { val ciphertext: ByteArray when (destination) { is Destination.Contact -> ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, destination.publicKey) - is Destination.ClosedGroup -> ciphertext = MessageSenderEncryption.encryptWithSharedSenderKeys(plaintext, destination.groupPublicKey) + is Destination.ClosedGroup -> { + val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey) + ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey) + } is Destination.OpenGroup -> throw preconditionFailure } // Wrap the result 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/MessageSenderEncryption.kt index 2b0f390a45..7ee8a47f8a 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/MessageSenderEncryption.kt @@ -38,7 +38,7 @@ object MessageSenderEncryption { return MessagingConfiguration.shared.sessionProtocol.encrypt(plaintext, recipientPublicKey) } - internal fun encryptWithSharedSenderKeys(plaintext: ByteArray, groupPublicKey: String): ByteArray { + /*internal fun encryptWithSharedSenderKeys(plaintext: ByteArray, groupPublicKey: String): ByteArray { // 1. ) Encrypt the data with the user's sender key val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: throw Error.NoUserPublicKey val ciphertextAndKeyIndex = SharedSenderKeysImplementation.shared.encrypt(plaintext, groupPublicKey, userPublicKey) @@ -52,5 +52,5 @@ object MessageSenderEncryption { .setCiphertext(ByteString.copyFrom(intermediate.ciphertext)) .setEphemeralPublicKey(ByteString.copyFrom(intermediate.ephemeralPublicKey)) .build().toByteArray() - } + }*/ } \ No newline at end of file