Remove weird roundabout way of doing decryption

This commit is contained in:
Niels Andriesse 2021-05-13 15:27:08 +10:00
parent d83c257491
commit d9348c5442
12 changed files with 37 additions and 147 deletions

View File

@ -70,7 +70,6 @@ import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker; import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager; import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
import org.thoughtcrime.securesms.loki.api.PublicChatManager; import org.thoughtcrime.securesms.loki.api.PublicChatManager;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase; import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
@ -178,8 +177,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
MessagingModuleConfiguration.Companion.configure(this, MessagingModuleConfiguration.Companion.configure(this,
DatabaseFactory.getStorage(this), DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this), DatabaseFactory.getAttachmentProvider(this));
new SessionProtocolImpl(this));
SnodeModule.Companion.configure(apiDB, broadcaster); SnodeModule.Companion.configure(apiDB, broadcaster);
if (userPublicKey != null) { if (userPublicKey != null) {
MentionsManager.Companion.configureIfNeeded(userPublicKey, userDB); MentionsManager.Companion.configureIfNeeded(userPublicKey, userDB);

View File

@ -347,7 +347,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(group, server) DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(group, server)
} }
override fun isMessageDuplicated(timestamp: Long, sender: String): Boolean { override fun isDuplicateMessage(timestamp: Long, sender: String): Boolean {
return getReceivedMessageTimestamps().contains(timestamp) return getReceivedMessageTimestamps().contains(timestamp)
} }

View File

@ -24,6 +24,7 @@ import network.loki.messenger.R
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all import nl.komponents.kovenant.all
import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.avatars.AvatarHelper import org.session.libsession.messaging.avatars.AvatarHelper
import org.session.libsession.messaging.open_groups.OpenGroupAPI import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
@ -189,7 +190,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
promises.add(ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, this)) promises.add(ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, this))
} }
val compoundPromise = all(promises) val compoundPromise = all(promises)
compoundPromise.success { compoundPromise.successUi { // Do this on the UI thread so that it happens before the alwaysUi clause below
if (isUpdatingProfilePicture && profilePicture != null) { if (isUpdatingProfilePicture && profilePicture != null) {
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture) AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt()) TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
@ -206,7 +207,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
btnGroupNameDisplay.text = displayName btnGroupNameDisplay.text = displayName
} }
if (isUpdatingProfilePicture && profilePicture != null) { if (isUpdatingProfilePicture && profilePicture != null) {
profilePictureView.recycle() // clear cached image before update tje profilePictureView profilePictureView.recycle() // Clear the cached image before updating
profilePictureView.update() profilePictureView.update()
} }
displayNameToBeUploaded = null displayNameToBeUploaded = null

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.session.libsession.messaging.sending_receiving.*
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
@ -15,12 +16,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.generateAndSendNewEncryptionKeyPair
import org.session.libsession.messaging.sending_receiving.pendingKeyPair
import org.session.libsession.messaging.sending_receiving.sendEncryptionKeyPair
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.GroupRecord
@ -330,7 +326,7 @@ object ClosedGroupsProtocolV2 {
// Find our wrapper and decrypt it if possible // Find our wrapper and decrypt it if possible
val wrapper = closedGroupUpdate.wrappersList.firstOrNull { it.publicKey.toByteArray().toHexString() == userPublicKey } ?: return val wrapper = closedGroupUpdate.wrappersList.firstOrNull { it.publicKey.toByteArray().toHexString() == userPublicKey } ?: return
val encryptedKeyPair = wrapper.encryptedKeyPair.toByteArray() val encryptedKeyPair = wrapper.encryptedKeyPair.toByteArray()
val plaintext = SessionProtocolImpl(context).decrypt(encryptedKeyPair, userKeyPair).first val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first
// Parse it // Parse it
val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext) val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext)
val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray())) val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray()))

View File

@ -2,13 +2,11 @@ package org.session.libsession.messaging
import android.content.Context import android.content.Context
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
class MessagingModuleConfiguration( class MessagingModuleConfiguration(
val context: Context, val context: Context,
val storage: StorageProtocol, val storage: StorageProtocol,
val messageDataProvider: MessageDataProvider, val messageDataProvider: MessageDataProvider
val sessionProtocol: SessionProtocol
) { ) {
companion object { companion object {
@ -16,11 +14,10 @@ class MessagingModuleConfiguration(
fun configure(context: Context, fun configure(context: Context,
storage: StorageProtocol, storage: StorageProtocol,
messageDataProvider: MessageDataProvider, messageDataProvider: MessageDataProvider
sessionProtocol: SessionProtocol
) { ) {
if (Companion::shared.isInitialized) { return } if (Companion::shared.isInitialized) { return }
shared = MessagingModuleConfiguration(context, storage, messageDataProvider, sessionProtocol) shared = MessagingModuleConfiguration(context, storage, messageDataProvider)
} }
} }
} }

View File

@ -92,7 +92,7 @@ interface StorageProtocol {
fun removeLastDeletionServerId(room: String, server: String) fun removeLastDeletionServerId(room: String, server: String)
// Message Handling // Message Handling
fun isMessageDuplicated(timestamp: Long, sender: String): Boolean fun isDuplicateMessage(timestamp: Long, sender: String): Boolean
fun getReceivedMessageTimestamps(): Set<Long> fun getReceivedMessageTimestamps(): Set<Long>
fun addReceivedMessageTimestamp(timestamp: Long) fun addReceivedMessageTimestamp(timestamp: Long)
fun removeReceivedMessageTimestamps(timestamps: Set<Long>) fun removeReceivedMessageTimestamps(timestamps: Set<Long>)

View File

@ -1,26 +1,21 @@
package org.thoughtcrime.securesms.loki.api package org.session.libsession.messaging.sending_receiving
import android.content.Context
import android.util.Log import android.util.Log
import com.goterl.lazycode.lazysodium.LazySodiumAndroid import com.goterl.lazycode.lazysodium.LazySodiumAndroid
import com.goterl.lazycode.lazysodium.SodiumAndroid import com.goterl.lazycode.lazysodium.SodiumAndroid
import com.goterl.lazycode.lazysodium.interfaces.Box import com.goterl.lazycode.lazysodium.interfaces.Box
import com.goterl.lazycode.lazysodium.interfaces.Sign import com.goterl.lazycode.lazysodium.interfaces.Sign
import org.session.libsignal.utilities.Hex
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsession.utilities.KeyPairUtilities import org.session.libsignal.utilities.Hex
class SessionProtocolImpl(private val context: Context) : SessionProtocol { object MessageDecrypter {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
override fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String> { public fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String> {
val recipientX25519PrivateKey = x25519KeyPair.privateKey.serialize() val recipientX25519PrivateKey = x25519KeyPair.privateKey.serialize()
val recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded()) val recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded())
val signatureSize = Sign.BYTES val signatureSize = Sign.BYTES
@ -32,9 +27,9 @@ class SessionProtocolImpl(private val context: Context) : SessionProtocol {
sodium.cryptoBoxSealOpen(plaintextWithMetadata, ciphertext, ciphertext.size.toLong(), recipientX25519PublicKey, recipientX25519PrivateKey) sodium.cryptoBoxSealOpen(plaintextWithMetadata, ciphertext, ciphertext.size.toLong(), recipientX25519PublicKey, recipientX25519PrivateKey)
} catch (exception: Exception) { } catch (exception: Exception) {
Log.d("Loki", "Couldn't decrypt message due to error: $exception.") Log.d("Loki", "Couldn't decrypt message due to error: $exception.")
throw SessionProtocol.Exception.DecryptionFailed throw MessageReceiver.Error.DecryptionFailed
} }
if (plaintextWithMetadata.size <= (signatureSize + ed25519PublicKeySize)) { throw SessionProtocol.Exception.DecryptionFailed } if (plaintextWithMetadata.size <= (signatureSize + ed25519PublicKeySize)) { throw MessageReceiver.Error.DecryptionFailed }
// 2. ) Get the message parts // 2. ) Get the message parts
val signature = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - signatureSize until plaintextWithMetadata.size) val signature = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - signatureSize until plaintextWithMetadata.size)
val senderED25519PublicKey = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize) until plaintextWithMetadata.size - signatureSize) val senderED25519PublicKey = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize) until plaintextWithMetadata.size - signatureSize)
@ -43,10 +38,10 @@ class SessionProtocolImpl(private val context: Context) : SessionProtocol {
val verificationData = (plaintext + senderED25519PublicKey + recipientX25519PublicKey) val verificationData = (plaintext + senderED25519PublicKey + recipientX25519PublicKey)
try { try {
val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey) val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey)
if (!isValid) { throw SessionProtocol.Exception.InvalidSignature } if (!isValid) { throw MessageReceiver.Error.InvalidSignature }
} catch (exception: Exception) { } catch (exception: Exception) {
Log.d("Loki", "Couldn't verify message signature due to error: $exception.") Log.d("Loki", "Couldn't verify message signature due to error: $exception.")
throw SessionProtocol.Exception.InvalidSignature throw MessageReceiver.Error.InvalidSignature
} }
// 4. ) Get the sender's X25519 public key // 4. ) Get the sender's X25519 public key
val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES) val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES)

View File

@ -10,35 +10,25 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
object MessageReceiver { object MessageReceiver {
private val lastEncryptionKeyPairRequest = mutableMapOf<String, Long>() internal sealed class Error(message: String) : Exception(message) {
internal sealed class Error(val description: String) : Exception(description) {
object DuplicateMessage: Error("Duplicate message.") object DuplicateMessage: Error("Duplicate message.")
object InvalidMessage: Error("Invalid message.") object InvalidMessage: Error("Invalid message.")
object UnknownMessage: Error("Unknown message type.") object UnknownMessage: Error("Unknown message type.")
object UnknownEnvelopeType: Error("Unknown envelope type.") object UnknownEnvelopeType: Error("Unknown envelope type.")
object NoUserX25519KeyPair: Error("Couldn't find user X25519 key pair.") object DecryptionFailed : Exception("Couldn't decrypt message.")
object NoUserED25519KeyPair: Error("Couldn't find user ED25519 key pair.")
object InvalidSignature: Error("Invalid message signature.") object InvalidSignature: Error("Invalid message signature.")
object NoData: Error("Received an empty envelope.") object NoData: Error("Received an empty envelope.")
object SenderBlocked: Error("Received a message from a blocked user.") object SenderBlocked: Error("Received a message from a blocked user.")
object NoThread: Error("Couldn't find thread for message.") object NoThread: Error("Couldn't find thread for message.")
object SelfSend: Error("Message addressed at self.") object SelfSend: Error("Message addressed at self.")
object ParsingFailed : Error("Couldn't parse ciphertext message.")
// Shared sender keys // Shared sender keys
object InvalidGroupPublicKey: Error("Invalid group public key.") object InvalidGroupPublicKey: Error("Invalid group public key.")
object NoGroupKeyPair: Error("Missing group key pair.") object NoGroupKeyPair: Error("Missing group key pair.")
internal val isRetryable: Boolean = when (this) { internal val isRetryable: Boolean = when (this) {
is DuplicateMessage -> false is DuplicateMessage, is InvalidMessage, is UnknownMessage,
is InvalidMessage -> false is UnknownEnvelopeType, is InvalidSignature, is NoData,
is UnknownMessage -> false is SenderBlocked, is SelfSend -> false
is UnknownEnvelopeType -> false
is InvalidSignature -> false
is NoData -> false
is NoThread -> false
is SenderBlocked -> false
is SelfSend -> false
else -> true else -> true
} }
} }
@ -46,13 +36,15 @@ object MessageReceiver {
internal fun parse(data: ByteArray, openGroupServerID: Long?, isRetry: Boolean = false): Pair<Message, SignalServiceProtos.Content> { internal fun parse(data: ByteArray, openGroupServerID: Long?, isRetry: Boolean = false): Pair<Message, SignalServiceProtos.Content> {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey() val userPublicKey = storage.getUserPublicKey()
val isOpenGroupMessage = openGroupServerID != null val isOpenGroupMessage = (openGroupServerID != null)
// Parse the envelope // Parse the envelope
val envelope = SignalServiceProtos.Envelope.parseFrom(data) val envelope = SignalServiceProtos.Envelope.parseFrom(data)
// If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp // If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp
// will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround // will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround
// for this issue. // for this issue.
if (storage.isMessageDuplicated(envelope.timestamp, GroupUtil.doubleEncodeGroupID(envelope.source)) && !isRetry) throw Error.DuplicateMessage if (storage.isDuplicateMessage(envelope.timestamp, GroupUtil.doubleEncodeGroupID(envelope.source)) && !isRetry) {
throw Error.DuplicateMessage
}
// Decrypt the contents // Decrypt the contents
val ciphertext = envelope.content ?: throw Error.NoData val ciphertext = envelope.content ?: throw Error.NoData
var plaintext: ByteArray? = null var plaintext: ByteArray? = null
@ -65,7 +57,7 @@ object MessageReceiver {
when (envelope.type) { when (envelope.type) {
SignalServiceProtos.Envelope.Type.SESSION_MESSAGE -> { SignalServiceProtos.Envelope.Type.SESSION_MESSAGE -> {
val userX25519KeyPair = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair() val userX25519KeyPair = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair()
val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), userX25519KeyPair) val decryptionResult = MessageDecrypter.decrypt(ciphertext.toByteArray(), userX25519KeyPair)
plaintext = decryptionResult.first plaintext = decryptionResult.first
sender = decryptionResult.second sender = decryptionResult.second
} }
@ -81,7 +73,7 @@ object MessageReceiver {
var encryptionKeyPair = encryptionKeyPairs.removeLast() var encryptionKeyPair = encryptionKeyPairs.removeLast()
fun decrypt() { fun decrypt() {
try { try {
val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), encryptionKeyPair) val decryptionResult = MessageDecrypter.decrypt(ciphertext.toByteArray(), encryptionKeyPair)
plaintext = decryptionResult.first plaintext = decryptionResult.first
sender = decryptionResult.second sender = decryptionResult.second
} catch (e: Exception) { } catch (e: Exception) {
@ -100,9 +92,9 @@ object MessageReceiver {
} }
} }
// Don't process the envelope any further if the message has been handled already // Don't process the envelope any further if the message has been handled already
if (storage.isMessageDuplicated(envelope.timestamp, sender!!) && !isRetry) throw Error.DuplicateMessage if (storage.isDuplicateMessage(envelope.timestamp, sender!!) && !isRetry) throw Error.DuplicateMessage
// Don't process the envelope any further if the sender is blocked // Don't process the envelope any further if the sender is blocked
if (isBlock(sender!!)) throw Error.SenderBlocked if (isBlocked(sender!!)) throw Error.SenderBlocked
// Parse the proto // Parse the proto
val proto = SignalServiceProtos.Content.parseFrom(PushTransportDetails.getStrippedPaddingMessageBody(plaintext)) val proto = SignalServiceProtos.Content.parseFrom(PushTransportDetails.getStrippedPaddingMessageBody(plaintext))
// Parse the message // Parse the message
@ -113,7 +105,7 @@ object MessageReceiver {
ExpirationTimerUpdate.fromProto(proto) ?: ExpirationTimerUpdate.fromProto(proto) ?:
ConfigurationMessage.fromProto(proto) ?: ConfigurationMessage.fromProto(proto) ?:
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
// Ignore self sends if needed // Ignore self send if needed
if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend
// Guard against control messages in open groups // Guard against control messages in open groups
if (isOpenGroupMessage && message !is VisibleMessage) throw Error.InvalidMessage if (isOpenGroupMessage && message !is VisibleMessage) throw Error.InvalidMessage

View File

@ -1,11 +0,0 @@
package org.session.libsession.messaging.sending_receiving
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsignal.libsignal.ecc.ECKeyPair
object MessageReceiverDecryption {
internal fun decryptWithSessionProtocol(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String> {
return MessagingModuleConfiguration.shared.sessionProtocol.decrypt(ciphertext, x25519KeyPair)
}
}

View File

@ -34,7 +34,7 @@ import java.security.MessageDigest
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
internal fun MessageReceiver.isBlock(publicKey: String): Boolean { internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
val context = MessagingModuleConfiguration.shared.context val context = MessagingModuleConfiguration.shared.context
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
return recipient.isBlocked return recipient.isBlocked
@ -323,7 +323,7 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
// Find our wrapper and decrypt it if possible // Find our wrapper and decrypt it if possible
val wrapper = kind.wrappers.firstOrNull { it.publicKey!! == userPublicKey } ?: return val wrapper = kind.wrappers.firstOrNull { it.publicKey!! == userPublicKey } ?: return
val encryptedKeyPair = wrapper.encryptedKeyPair!!.toByteArray() val encryptedKeyPair = wrapper.encryptedKeyPair!!.toByteArray()
val plaintext = MessageReceiverDecryption.decryptWithSessionProtocol(encryptedKeyPair, userKeyPair).first val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first
// Parse it // Parse it
val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext) val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext)
val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray())) val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray()))

View File

@ -31,8 +31,6 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos.Content;
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol; import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol;
import java.util.ArrayList; import java.util.ArrayList;
@ -51,13 +49,10 @@ public class SignalServiceCipher {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = SignalServiceCipher.class.getSimpleName(); private static final String TAG = SignalServiceCipher.class.getSimpleName();
private final SessionProtocol sessionProtocolImpl;
private final LokiAPIDatabaseProtocol apiDB; private final LokiAPIDatabaseProtocol apiDB;
public SignalServiceCipher(SessionProtocol sessionProtocolImpl, public SignalServiceCipher(LokiAPIDatabaseProtocol apiDB)
LokiAPIDatabaseProtocol apiDB)
{ {
this.sessionProtocolImpl = sessionProtocolImpl;
this.apiDB = apiDB; this.apiDB = apiDB;
} }
@ -125,27 +120,7 @@ public class SignalServiceCipher {
protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) throws InvalidMetadataMessageException protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) throws InvalidMetadataMessageException
{ {
byte[] paddedMessage; throw new IllegalStateException("This shouldn't be used anymore");
Metadata metadata;
if (envelope.isClosedGroupCiphertext()) {
String groupPublicKey = envelope.getSource();
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = SessionProtocolUtilities.INSTANCE.decryptClosedGroupCiphertext(ciphertext, groupPublicKey, apiDB, sessionProtocolImpl);
paddedMessage = plaintextAndSenderPublicKey.getFirst();
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
} else if (envelope.isUnidentifiedSender()) {
ECKeyPair userX25519KeyPair = apiDB.getUserX25519KeyPair();
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(ciphertext, userX25519KeyPair);
paddedMessage = plaintextAndSenderPublicKey.getFirst();
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
} else {
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
}
byte[] data = PushTransportDetails.getStrippedPaddingMessageBody(paddedMessage);
return new Plaintext(metadata, data);
} }
private SignalServiceDataMessage createSignalServiceMessage(Metadata metadata, DataMessage content) throws ProtocolInvalidMessageException { private SignalServiceDataMessage createSignalServiceMessage(Metadata metadata, DataMessage content) throws ProtocolInvalidMessageException {

View File

@ -1,53 +0,0 @@
package org.session.libsignal.service.loki.api.crypto
import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
interface SessionProtocol {
sealed class Exception(val description: String) : kotlin.Exception(description) {
// Encryption
object NoUserED25519KeyPair : Exception("Couldn't find user ED25519 key pair.")
object SigningFailed : Exception("Couldn't sign message.")
object EncryptionFailed : Exception("Couldn't encrypt message.")
// Decryption
object NoData : Exception("Received an empty envelope.")
object InvalidGroupPublicKey : Exception("Invalid group public key.")
object NoGroupKeyPair : Exception("Missing group key pair.")
object DecryptionFailed : Exception("Couldn't decrypt message.")
object InvalidSignature : Exception("Invalid message signature.")
}
/**
* 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.
*/
fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String>
}
object SessionProtocolUtilities {
fun decryptClosedGroupCiphertext(ciphertext: ByteArray, groupPublicKey: String, apiDB: LokiAPIDatabaseProtocol, sessionProtocolImpl: SessionProtocol): Pair<ByteArray, String> {
val encryptionKeyPairs = apiDB.getClosedGroupEncryptionKeyPairs(groupPublicKey).toMutableList()
if (encryptionKeyPairs.isEmpty()) { throw SessionProtocol.Exception.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.removeAt(encryptionKeyPairs.lastIndex)
fun decrypt(): Pair<ByteArray, String> {
try {
return sessionProtocolImpl.decrypt(ciphertext, encryptionKeyPair)
} catch(exception: Exception) {
if (encryptionKeyPairs.isNotEmpty()) {
encryptionKeyPair = encryptionKeyPairs.removeAt(encryptionKeyPairs.lastIndex)
return decrypt()
} else {
throw exception
}
}
}
return decrypt()
}
}