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.LokiPushNotificationManager;
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.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
@ -178,8 +177,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
MessagingModuleConfiguration.Companion.configure(this,
DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this));
DatabaseFactory.getAttachmentProvider(this));
SnodeModule.Companion.configure(apiDB, broadcaster);
if (userPublicKey != null) {
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)
}
override fun isMessageDuplicated(timestamp: Long, sender: String): Boolean {
override fun isDuplicateMessage(timestamp: Long, sender: String): Boolean {
return getReceivedMessageTimestamps().contains(timestamp)
}

View File

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

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context
import android.util.Log
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.DjbECPublicKey
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.loki.api.LokiPushNotificationManager
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl
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.GroupRecord
@ -330,7 +326,7 @@ object ClosedGroupsProtocolV2 {
// Find our wrapper and decrypt it if possible
val wrapper = closedGroupUpdate.wrappersList.firstOrNull { it.publicKey.toByteArray().toHexString() == userPublicKey } ?: return
val encryptedKeyPair = wrapper.encryptedKeyPair.toByteArray()
val plaintext = SessionProtocolImpl(context).decrypt(encryptedKeyPair, userKeyPair).first
val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first
// Parse it
val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext)
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 org.session.libsession.database.MessageDataProvider
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
class MessagingModuleConfiguration(
val context: Context,
val storage: StorageProtocol,
val messageDataProvider: MessageDataProvider,
val sessionProtocol: SessionProtocol
val messageDataProvider: MessageDataProvider
) {
companion object {
@ -16,11 +14,10 @@ class MessagingModuleConfiguration(
fun configure(context: Context,
storage: StorageProtocol,
messageDataProvider: MessageDataProvider,
sessionProtocol: SessionProtocol
messageDataProvider: MessageDataProvider
) {
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)
// Message Handling
fun isMessageDuplicated(timestamp: Long, sender: String): Boolean
fun isDuplicateMessage(timestamp: Long, sender: String): Boolean
fun getReceivedMessageTimestamps(): Set<Long>
fun addReceivedMessageTimestamp(timestamp: 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 com.goterl.lazycode.lazysodium.LazySodiumAndroid
import com.goterl.lazycode.lazysodium.SodiumAndroid
import com.goterl.lazycode.lazysodium.interfaces.Box
import com.goterl.lazycode.lazysodium.interfaces.Sign
import org.session.libsignal.utilities.Hex
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.removing05PrefixIfNeeded
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()) }
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 recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded())
val signatureSize = Sign.BYTES
@ -32,9 +27,9 @@ class SessionProtocolImpl(private val context: Context) : SessionProtocol {
sodium.cryptoBoxSealOpen(plaintextWithMetadata, ciphertext, ciphertext.size.toLong(), recipientX25519PublicKey, recipientX25519PrivateKey)
} catch (exception: 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
val signature = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - signatureSize until plaintextWithMetadata.size)
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)
try {
val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey)
if (!isValid) { throw SessionProtocol.Exception.InvalidSignature }
if (!isValid) { throw MessageReceiver.Error.InvalidSignature }
} catch (exception: 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
val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES)

View File

@ -10,35 +10,25 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
object MessageReceiver {
private val lastEncryptionKeyPairRequest = mutableMapOf<String, Long>()
internal sealed class Error(val description: String) : Exception(description) {
internal sealed class Error(message: String) : Exception(message) {
object DuplicateMessage: Error("Duplicate message.")
object InvalidMessage: Error("Invalid message.")
object UnknownMessage: Error("Unknown message type.")
object UnknownEnvelopeType: Error("Unknown envelope type.")
object NoUserX25519KeyPair: Error("Couldn't find user X25519 key pair.")
object NoUserED25519KeyPair: Error("Couldn't find user ED25519 key pair.")
object DecryptionFailed : Exception("Couldn't decrypt message.")
object InvalidSignature: Error("Invalid message signature.")
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.")
object SelfSend: Error("Message addressed at self.")
object ParsingFailed : Error("Couldn't parse ciphertext message.")
// Shared sender keys
object InvalidGroupPublicKey: Error("Invalid group public key.")
object NoGroupKeyPair: Error("Missing group key pair.")
internal val isRetryable: Boolean = when (this) {
is DuplicateMessage -> false
is InvalidMessage -> false
is UnknownMessage -> false
is UnknownEnvelopeType -> false
is InvalidSignature -> false
is NoData -> false
is NoThread -> false
is SenderBlocked -> false
is SelfSend -> false
is DuplicateMessage, is InvalidMessage, is UnknownMessage,
is UnknownEnvelopeType, is InvalidSignature, is NoData,
is SenderBlocked, is SelfSend -> false
else -> true
}
}
@ -46,13 +36,15 @@ object MessageReceiver {
internal fun parse(data: ByteArray, openGroupServerID: Long?, isRetry: Boolean = false): Pair<Message, SignalServiceProtos.Content> {
val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()
val isOpenGroupMessage = openGroupServerID != null
val isOpenGroupMessage = (openGroupServerID != null)
// Parse the envelope
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
// 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.
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
val ciphertext = envelope.content ?: throw Error.NoData
var plaintext: ByteArray? = null
@ -65,7 +57,7 @@ object MessageReceiver {
when (envelope.type) {
SignalServiceProtos.Envelope.Type.SESSION_MESSAGE -> {
val userX25519KeyPair = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair()
val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), userX25519KeyPair)
val decryptionResult = MessageDecrypter.decrypt(ciphertext.toByteArray(), userX25519KeyPair)
plaintext = decryptionResult.first
sender = decryptionResult.second
}
@ -81,7 +73,7 @@ object MessageReceiver {
var encryptionKeyPair = encryptionKeyPairs.removeLast()
fun decrypt() {
try {
val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), encryptionKeyPair)
val decryptionResult = MessageDecrypter.decrypt(ciphertext.toByteArray(), encryptionKeyPair)
plaintext = decryptionResult.first
sender = decryptionResult.second
} catch (e: Exception) {
@ -100,9 +92,9 @@ object MessageReceiver {
}
}
// 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
if (isBlock(sender!!)) throw Error.SenderBlocked
if (isBlocked(sender!!)) throw Error.SenderBlocked
// Parse the proto
val proto = SignalServiceProtos.Content.parseFrom(PushTransportDetails.getStrippedPaddingMessageBody(plaintext))
// Parse the message
@ -113,7 +105,7 @@ object MessageReceiver {
ExpirationTimerUpdate.fromProto(proto) ?:
ConfigurationMessage.fromProto(proto) ?:
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
// Ignore self sends if needed
// Ignore self send if needed
if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend
// Guard against control messages in open groups
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 kotlin.collections.ArrayList
internal fun MessageReceiver.isBlock(publicKey: String): Boolean {
internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
val context = MessagingModuleConfiguration.shared.context
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
return recipient.isBlocked
@ -323,7 +323,7 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
// Find our wrapper and decrypt it if possible
val wrapper = kind.wrappers.firstOrNull { it.publicKey!! == userPublicKey } ?: return
val encryptedKeyPair = wrapper.encryptedKeyPair!!.toByteArray()
val plaintext = MessageReceiverDecryption.decryptWithSessionProtocol(encryptedKeyPair, userKeyPair).first
val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first
// Parse it
val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext)
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.ReceiptMessage;
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 java.util.ArrayList;
@ -51,13 +49,10 @@ public class SignalServiceCipher {
@SuppressWarnings("unused")
private static final String TAG = SignalServiceCipher.class.getSimpleName();
private final SessionProtocol sessionProtocolImpl;
private final LokiAPIDatabaseProtocol apiDB;
public SignalServiceCipher(SessionProtocol sessionProtocolImpl,
LokiAPIDatabaseProtocol apiDB)
public SignalServiceCipher(LokiAPIDatabaseProtocol apiDB)
{
this.sessionProtocolImpl = sessionProtocolImpl;
this.apiDB = apiDB;
}
@ -125,27 +120,7 @@ public class SignalServiceCipher {
protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) throws InvalidMetadataMessageException
{
byte[] paddedMessage;
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);
throw new IllegalStateException("This shouldn't be used anymore");
}
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()
}
}