mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
parent
9002606917
commit
e33b8a5fa2
@ -562,7 +562,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearData() {
|
//FIXME AC: Using this method to cleanup app data is unsafe due to potential concurrent
|
||||||
|
// activity that still might be using the data that is being deleted here.
|
||||||
|
// The most reliable and safe way to do this is to use official API call:
|
||||||
|
// https://developer.android.com/reference/android/app/ActivityManager.html#clearApplicationUserData()
|
||||||
|
// The downside is it kills the app in the process and there's no any conventional way to start
|
||||||
|
// another activity when the task is done.
|
||||||
|
// Dev community is in demand for such a feature, so check on it some time in the feature
|
||||||
|
// and replace our implementation with the API call when it's safe to do so.
|
||||||
|
// Here's a feature request related https://issuetracker.google.com/issues/174903931
|
||||||
|
public void clearAllData() {
|
||||||
String token = TextSecurePreferences.getFCMToken(this);
|
String token = TextSecurePreferences.getFCMToken(this);
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
LokiPushNotificationManager.unregister(token, this);
|
LokiPushNotificationManager.unregister(token, this);
|
||||||
|
@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
|
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
|
||||||
@ -100,8 +101,8 @@ public class SignalCommunicationModule {
|
|||||||
DatabaseFactory.getSSKDatabase(context),
|
DatabaseFactory.getSSKDatabase(context),
|
||||||
DatabaseFactory.getLokiThreadDatabase(context),
|
DatabaseFactory.getLokiThreadDatabase(context),
|
||||||
DatabaseFactory.getLokiMessageDatabase(context),
|
DatabaseFactory.getLokiMessageDatabase(context),
|
||||||
// DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
null, // DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
||||||
null,
|
new SessionProtocolImpl(context),
|
||||||
new SessionResetImplementation(context),
|
new SessionResetImplementation(context),
|
||||||
DatabaseFactory.getLokiUserDatabase(context),
|
DatabaseFactory.getLokiUserDatabase(context),
|
||||||
DatabaseFactory.getGroupDatabase(context),
|
DatabaseFactory.getGroupDatabase(context),
|
||||||
|
@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
|
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||||
@ -244,7 +245,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||||
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
||||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
||||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
||||||
|
|
||||||
SignalServiceContent content = cipher.decrypt(envelope);
|
SignalServiceContent content = cipher.decrypt(envelope);
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
// Clear all data if this is a secondary device
|
// Clear all data if this is a secondary device
|
||||||
if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) != null) {
|
if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) != null) {
|
||||||
TextSecurePreferences.setWasUnlinked(this, true)
|
TextSecurePreferences.setWasUnlinked(this, true)
|
||||||
ApplicationContext.getInstance(this).clearData()
|
ApplicationContext.getInstance(this).clearAllData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform chat sessions reset if requested (usually happens after backup restoration).
|
// Perform chat sessions reset if requested (usually happens after backup restoration).
|
||||||
@ -220,36 +220,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
if (hasViewedSeed || !isMasterDevice) {
|
if (hasViewedSeed || !isMasterDevice) {
|
||||||
seedReminderView.visibility = View.GONE
|
seedReminderView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi device removal sheet
|
|
||||||
if (!TextSecurePreferences.getHasSeenMultiDeviceRemovalSheet(this)) {
|
|
||||||
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
|
||||||
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey)
|
|
||||||
if (deviceLinks.isNotEmpty()) {
|
|
||||||
val bottomSheet = MultiDeviceRemovalBottomSheet()
|
|
||||||
bottomSheet.onOKTapped = {
|
|
||||||
bottomSheet.dismiss()
|
|
||||||
}
|
|
||||||
bottomSheet.onLinkTapped = {
|
|
||||||
bottomSheet.dismiss()
|
|
||||||
val url = "https://getsession.org/faq"
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Light theme introduction sheet
|
|
||||||
if (!TextSecurePreferences.hasSeenLightThemeIntroSheet(this) &&
|
|
||||||
UiModeUtilities.isDayUiMode(this)) {
|
|
||||||
TextSecurePreferences.setHasSeenLightThemeIntroSheet(this)
|
|
||||||
val bottomSheet = LightThemeFeatureIntroBottomSheet()
|
|
||||||
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.api
|
||||||
|
|
||||||
|
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.libsignal.util.Hex
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||||
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE
|
||||||
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE
|
||||||
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
||||||
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
|
||||||
|
class SessionProtocolImpl(private val context: Context) : SessionProtocol {
|
||||||
|
|
||||||
|
override fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray {
|
||||||
|
val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw SessionProtocol.Exception.NoUserED25519KeyPair
|
||||||
|
val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
|
||||||
|
val sodium = LazySodiumAndroid(SodiumAndroid())
|
||||||
|
|
||||||
|
val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey
|
||||||
|
val signature = ByteArray(Sign.BYTES)
|
||||||
|
try {
|
||||||
|
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.d("Loki", "Couldn't sign message due to error: $exception.")
|
||||||
|
throw SessionProtocol.Exception.SigningFailed
|
||||||
|
}
|
||||||
|
val plaintextWithMetadata = plaintext + userED25519KeyPair.publicKey.asBytes + signature
|
||||||
|
val ciphertext = ByteArray(plaintextWithMetadata.size + Box.SEALBYTES)
|
||||||
|
try {
|
||||||
|
sodium.cryptoBoxSeal(ciphertext, plaintextWithMetadata, plaintextWithMetadata.size.toLong(), recipientX25519PublicKey)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.d("Loki", "Couldn't encrypt message due to error: $exception.")
|
||||||
|
throw SessionProtocol.Exception.EncryptionFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
return ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String> {
|
||||||
|
val ciphertext = envelope.content ?: throw SessionProtocol.Exception.NoData
|
||||||
|
val recipientX25519PrivateKey: ByteArray
|
||||||
|
val recipientX25519PublicKey: ByteArray
|
||||||
|
when (envelope.type) {
|
||||||
|
UNIDENTIFIED_SENDER_VALUE -> {
|
||||||
|
recipientX25519PrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||||
|
recipientX25519PublicKey = Hex.fromStringCondensed(TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded())
|
||||||
|
}
|
||||||
|
CLOSED_GROUP_CIPHERTEXT_VALUE -> {
|
||||||
|
val hexEncodedGroupPublicKey = envelope.source
|
||||||
|
val sskDB = DatabaseFactory.getSSKDatabase(context)
|
||||||
|
if (!sskDB.isSSKBasedClosedGroup(hexEncodedGroupPublicKey)) { throw SessionProtocol.Exception.InvalidGroupPublicKey }
|
||||||
|
val hexEncodedGroupPrivateKey = sskDB.getClosedGroupPrivateKey(hexEncodedGroupPublicKey) ?: throw SessionProtocol.Exception.NoGroupPrivateKey
|
||||||
|
recipientX25519PrivateKey = Hex.fromStringCondensed(hexEncodedGroupPrivateKey)
|
||||||
|
recipientX25519PublicKey = Hex.fromStringCondensed(hexEncodedGroupPublicKey.removing05PrefixIfNeeded())
|
||||||
|
}
|
||||||
|
else -> throw AssertionError()
|
||||||
|
}
|
||||||
|
val sodium = LazySodiumAndroid(SodiumAndroid())
|
||||||
|
val signatureSize = Sign.BYTES
|
||||||
|
val ed25519PublicKeySize = Sign.PUBLICKEYBYTES
|
||||||
|
|
||||||
|
// 1. ) Decrypt the message
|
||||||
|
val plaintextWithMetadata = ByteArray(ciphertext.size - Box.SEALBYTES)
|
||||||
|
try {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if (plaintextWithMetadata.size <= (signatureSize + ed25519PublicKeySize)) { throw SessionProtocol.Exception.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)
|
||||||
|
val plaintext = plaintextWithMetadata.sliceArray(0 until plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize))
|
||||||
|
// 3. ) Verify the signature
|
||||||
|
val verificationData = (plaintext + senderED25519PublicKey + recipientX25519PublicKey)
|
||||||
|
try {
|
||||||
|
val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey)
|
||||||
|
if (!isValid) { throw SessionProtocol.Exception.InvalidSignature }
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.d("Loki", "Couldn't verify message signature due to error: $exception.")
|
||||||
|
throw SessionProtocol.Exception.InvalidSignature
|
||||||
|
}
|
||||||
|
// 4. ) Get the sender's X25519 public key
|
||||||
|
val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES)
|
||||||
|
sodium.convertPublicKeyEd25519ToCurve25519(senderX25519PublicKey, senderED25519PublicKey)
|
||||||
|
|
||||||
|
return Pair(plaintext, "05" + senderX25519PublicKey.toHexString())
|
||||||
|
}
|
||||||
|
}
|
@ -27,13 +27,13 @@ class ClearAllDataDialog : DialogFragment() {
|
|||||||
|
|
||||||
private fun clearAllData() {
|
private fun clearAllData() {
|
||||||
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
|
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
|
||||||
ApplicationContext.getInstance(context).clearData()
|
ApplicationContext.getInstance(context).clearAllData()
|
||||||
} else {
|
} else {
|
||||||
val dialog = AlertDialog.Builder(requireContext())
|
val dialog = AlertDialog.Builder(requireContext())
|
||||||
val message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
|
val message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
|
||||||
dialog.setMessage(message)
|
dialog.setMessage(message)
|
||||||
dialog.setPositiveButton("Yes") { _, _ ->
|
dialog.setPositiveButton("Yes") { _, _ ->
|
||||||
ApplicationContext.getInstance(context).clearData()
|
ApplicationContext.getInstance(context).clearAllData()
|
||||||
}
|
}
|
||||||
dialog.setNegativeButton("Cancel") { _, _ ->
|
dialog.setNegativeButton("Cancel") { _, _ ->
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.utilities
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
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.utils.Key
|
||||||
import com.goterl.lazycode.lazysodium.utils.KeyPair
|
import com.goterl.lazycode.lazysodium.utils.KeyPair
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.util.Base64
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
@ -44,6 +45,13 @@ object KeyPairUtilities {
|
|||||||
return (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) != null)
|
return (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUserED25519KeyPair(context: Context): KeyPair? {
|
||||||
|
val hexEncodedED25519PublicKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_PUBLIC_KEY) ?: return null
|
||||||
|
val hexEncodedED25519SecretKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) ?: return null
|
||||||
|
val ed25519PublicKey = Key.fromBase64String(hexEncodedED25519PublicKey)
|
||||||
|
val ed25519SecretKey = Key.fromBase64String(hexEncodedED25519SecretKey)
|
||||||
|
return KeyPair(ed25519PublicKey, ed25519SecretKey)
|
||||||
|
}
|
||||||
|
|
||||||
data class KeyPairGenerationResult(
|
data class KeyPairGenerationResult(
|
||||||
val seed: ByteArray,
|
val seed: ByteArray,
|
||||||
|
@ -77,6 +77,7 @@ import org.session.libsignal.service.loki.api.LokiDotNetAPI;
|
|||||||
import org.session.libsignal.service.loki.api.PushNotificationAPI;
|
import org.session.libsignal.service.loki.api.PushNotificationAPI;
|
||||||
import org.session.libsignal.service.loki.api.SignalMessageInfo;
|
import org.session.libsignal.service.loki.api.SignalMessageInfo;
|
||||||
import org.session.libsignal.service.loki.api.SnodeAPI;
|
import org.session.libsignal.service.loki.api.SnodeAPI;
|
||||||
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
||||||
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
|
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
|
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
|
||||||
@ -142,6 +143,7 @@ public class SignalServiceMessageSender {
|
|||||||
private final LokiThreadDatabaseProtocol threadDatabase;
|
private final LokiThreadDatabaseProtocol threadDatabase;
|
||||||
private final LokiMessageDatabaseProtocol messageDatabase;
|
private final LokiMessageDatabaseProtocol messageDatabase;
|
||||||
private final LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase;
|
private final LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase;
|
||||||
|
private final SessionProtocol sessionProtocolImpl;
|
||||||
private final SessionResetProtocol sessionResetImpl;
|
private final SessionResetProtocol sessionResetImpl;
|
||||||
private final LokiUserDatabaseProtocol userDatabase;
|
private final LokiUserDatabaseProtocol userDatabase;
|
||||||
private final LokiOpenGroupDatabaseProtocol openGroupDatabase;
|
private final LokiOpenGroupDatabaseProtocol openGroupDatabase;
|
||||||
@ -171,12 +173,13 @@ public class SignalServiceMessageSender {
|
|||||||
LokiThreadDatabaseProtocol threadDatabase,
|
LokiThreadDatabaseProtocol threadDatabase,
|
||||||
LokiMessageDatabaseProtocol messageDatabase,
|
LokiMessageDatabaseProtocol messageDatabase,
|
||||||
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
||||||
|
SessionProtocol sessionProtocolImpl,
|
||||||
SessionResetProtocol sessionResetImpl,
|
SessionResetProtocol sessionResetImpl,
|
||||||
LokiUserDatabaseProtocol userDatabase,
|
LokiUserDatabaseProtocol userDatabase,
|
||||||
LokiOpenGroupDatabaseProtocol openGroupDatabase,
|
LokiOpenGroupDatabaseProtocol openGroupDatabase,
|
||||||
Broadcaster broadcaster)
|
Broadcaster broadcaster)
|
||||||
{
|
{
|
||||||
this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, sskDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster);
|
this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, sskDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionProtocolImpl, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
||||||
@ -193,6 +196,7 @@ public class SignalServiceMessageSender {
|
|||||||
LokiThreadDatabaseProtocol threadDatabase,
|
LokiThreadDatabaseProtocol threadDatabase,
|
||||||
LokiMessageDatabaseProtocol messageDatabase,
|
LokiMessageDatabaseProtocol messageDatabase,
|
||||||
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
||||||
|
SessionProtocol sessionProtocolImpl,
|
||||||
SessionResetProtocol sessionResetImpl,
|
SessionResetProtocol sessionResetImpl,
|
||||||
LokiUserDatabaseProtocol userDatabase,
|
LokiUserDatabaseProtocol userDatabase,
|
||||||
LokiOpenGroupDatabaseProtocol openGroupDatabase,
|
LokiOpenGroupDatabaseProtocol openGroupDatabase,
|
||||||
@ -211,6 +215,7 @@ public class SignalServiceMessageSender {
|
|||||||
this.threadDatabase = threadDatabase;
|
this.threadDatabase = threadDatabase;
|
||||||
this.messageDatabase = messageDatabase;
|
this.messageDatabase = messageDatabase;
|
||||||
this.preKeyBundleDatabase = preKeyBundleDatabase;
|
this.preKeyBundleDatabase = preKeyBundleDatabase;
|
||||||
|
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||||
this.sessionResetImpl = sessionResetImpl;
|
this.sessionResetImpl = sessionResetImpl;
|
||||||
this.userDatabase = userDatabase;
|
this.userDatabase = userDatabase;
|
||||||
this.openGroupDatabase = openGroupDatabase;
|
this.openGroupDatabase = openGroupDatabase;
|
||||||
@ -1383,6 +1388,30 @@ public class SignalServiceMessageSender {
|
|||||||
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, messages, online);
|
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, messages, online);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OutgoingPushMessageList getSessionProtocolEncryptedMessages(PushServiceSocket socket,
|
||||||
|
SignalServiceAddress recipient,
|
||||||
|
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||||
|
long timestamp,
|
||||||
|
byte[] plaintext,
|
||||||
|
boolean online,
|
||||||
|
boolean useFallbackEncryption,
|
||||||
|
boolean isClosedGroup)
|
||||||
|
{
|
||||||
|
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||||
|
|
||||||
|
PushTransportDetails transportDetails = new PushTransportDetails(3);
|
||||||
|
String publicKey = recipient.getNumber(); // Could be a contact's public key or the public key of a SSK group
|
||||||
|
byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), publicKey);
|
||||||
|
String body = Base64.encodeBytes(ciphertext);
|
||||||
|
boolean isSSKBasedClosedGroup = sskDatabase.isSSKBasedClosedGroup(publicKey);
|
||||||
|
int type = isSSKBasedClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
||||||
|
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||||
|
OutgoingPushMessage message = new OutgoingPushMessage(type, 1, 0, body);
|
||||||
|
messages.add(message);
|
||||||
|
|
||||||
|
return new OutgoingPushMessageList(publicKey, timestamp, messages, online);
|
||||||
|
}
|
||||||
|
|
||||||
private OutgoingPushMessage getUnencryptedMessage(byte[] plaintext) {
|
private OutgoingPushMessage getUnencryptedMessage(byte[] plaintext) {
|
||||||
Log.d("Loki", "Bypassing cipher and preparing a plaintext message.");
|
Log.d("Loki", "Bypassing cipher and preparing a plaintext message.");
|
||||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||||
@ -1417,7 +1446,7 @@ public class SignalServiceMessageSender {
|
|||||||
{
|
{
|
||||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(groupPublicKey, deviceID);
|
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(groupPublicKey, deviceID);
|
||||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, null);
|
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, sessionProtocolImpl, null);
|
||||||
try {
|
try {
|
||||||
return cipher.encrypt(signalProtocolAddress, Optional.of(unidentifiedAccess), plaintext);
|
return cipher.encrypt(signalProtocolAddress, Optional.of(unidentifiedAccess), plaintext);
|
||||||
} catch (org.session.libsignal.libsignal.UntrustedIdentityException e) {
|
} catch (org.session.libsignal.libsignal.UntrustedIdentityException e) {
|
||||||
@ -1435,7 +1464,7 @@ public class SignalServiceMessageSender {
|
|||||||
Log.d("Loki", "Using Signal cipher.");
|
Log.d("Loki", "Using Signal cipher.");
|
||||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getNumber(), deviceID);
|
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getNumber(), deviceID);
|
||||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, null);
|
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, sessionProtocolImpl, null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String contactPublicKey = recipient.getNumber();
|
String contactPublicKey = recipient.getNumber();
|
||||||
|
@ -83,6 +83,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos.SyncMessa
|
|||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.Verified;
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.Verified;
|
||||||
import org.session.libsignal.service.internal.util.Base64;
|
import org.session.libsignal.service.internal.util.Base64;
|
||||||
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities;
|
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities;
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
|
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
|
||||||
@ -114,18 +115,21 @@ public class SignalServiceCipher {
|
|||||||
private final SessionResetProtocol sessionResetProtocol;
|
private final SessionResetProtocol sessionResetProtocol;
|
||||||
private final SharedSenderKeysDatabaseProtocol sskDatabase;
|
private final SharedSenderKeysDatabaseProtocol sskDatabase;
|
||||||
private final SignalServiceAddress localAddress;
|
private final SignalServiceAddress localAddress;
|
||||||
|
private final SessionProtocol sessionProtocolImpl;
|
||||||
private final CertificateValidator certificateValidator;
|
private final CertificateValidator certificateValidator;
|
||||||
|
|
||||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||||
SignalProtocolStore signalProtocolStore,
|
SignalProtocolStore signalProtocolStore,
|
||||||
SharedSenderKeysDatabaseProtocol sskDatabase,
|
SharedSenderKeysDatabaseProtocol sskDatabase,
|
||||||
SessionResetProtocol sessionResetProtocol,
|
SessionResetProtocol sessionResetProtocol,
|
||||||
|
SessionProtocol sessionProtocolImpl,
|
||||||
CertificateValidator certificateValidator)
|
CertificateValidator certificateValidator)
|
||||||
{
|
{
|
||||||
this.signalProtocolStore = signalProtocolStore;
|
this.signalProtocolStore = signalProtocolStore;
|
||||||
this.sessionResetProtocol = sessionResetProtocol;
|
this.sessionResetProtocol = sessionResetProtocol;
|
||||||
this.sskDatabase = sskDatabase;
|
this.sskDatabase = sskDatabase;
|
||||||
this.localAddress = localAddress;
|
this.localAddress = localAddress;
|
||||||
|
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||||
this.certificateValidator = certificateValidator;
|
this.certificateValidator = certificateValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,12 +321,23 @@ public class SignalServiceCipher {
|
|||||||
int sessionVersion;
|
int sessionVersion;
|
||||||
|
|
||||||
if (envelope.isClosedGroupCiphertext()) {
|
if (envelope.isClosedGroupCiphertext()) {
|
||||||
Pair<byte[], String> plaintextAndSenderPublicKey = ClosedGroupUtilities.decrypt(envelope);
|
try {
|
||||||
String senderPublicKey = plaintextAndSenderPublicKey.second();
|
// Try the Session protocol
|
||||||
if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob
|
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(envelope);
|
||||||
paddedMessage = plaintextAndSenderPublicKey.first();
|
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||||
metadata = new Metadata(senderPublicKey, envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||||
sessionVersion = sessionCipher.getSessionVersion();
|
if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob
|
||||||
|
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||||
|
sessionVersion = sessionCipher.getSessionVersion();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
// Fall back on shared sender keys
|
||||||
|
Pair<byte[], String> plaintextAndSenderPublicKey = ClosedGroupUtilities.decrypt(envelope);
|
||||||
|
String senderPublicKey = plaintextAndSenderPublicKey.second();
|
||||||
|
if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob
|
||||||
|
paddedMessage = plaintextAndSenderPublicKey.first();
|
||||||
|
metadata = new Metadata(senderPublicKey, envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||||
|
sessionVersion = sessionCipher.getSessionVersion();
|
||||||
|
}
|
||||||
} else if (envelope.isPreKeySignalMessage()) {
|
} else if (envelope.isPreKeySignalMessage()) {
|
||||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
||||||
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||||
@ -332,11 +347,21 @@ public class SignalServiceCipher {
|
|||||||
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||||
sessionVersion = sessionCipher.getSessionVersion();
|
sessionVersion = sessionCipher.getSessionVersion();
|
||||||
} else if (envelope.isUnidentifiedSender()) {
|
} else if (envelope.isUnidentifiedSender()) {
|
||||||
Pair<SignalProtocolAddress, Pair<Integer, byte[]>> results = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerTimestamp(), envelope.getSource());
|
try {
|
||||||
Pair<Integer, byte[]> data = results.second();
|
// Try the Session protocol
|
||||||
paddedMessage = data.second();
|
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(envelope);
|
||||||
metadata = new Metadata(results.first().getName(), results.first().getDeviceId(), envelope.getTimestamp(), false);
|
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||||
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||||
|
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||||
|
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
||||||
|
} catch (Exception exception) {
|
||||||
|
// Fall back on the Signal protocol
|
||||||
|
Pair<SignalProtocolAddress, Pair<Integer, byte[]>> results = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerTimestamp(), envelope.getSource());
|
||||||
|
Pair<Integer, byte[]> data = results.second();
|
||||||
|
paddedMessage = data.second();
|
||||||
|
metadata = new Metadata(results.first().getName(), results.first().getDeviceId(), envelope.getTimestamp(), false);
|
||||||
|
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.session.libsignal.service.loki.api.crypto
|
||||||
|
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||||
|
|
||||||
|
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 NoGroupPrivateKey : Exception("Missing group private key.")
|
||||||
|
object DecryptionFailed : Exception("Couldn't decrypt message.")
|
||||||
|
object InvalidSignature : Exception("Invalid message signature.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts `plaintext` using the Session protocol for `hexEncodedX25519PublicKey`.
|
||||||
|
*
|
||||||
|
* @param plaintext the plaintext to encrypt. Must already be padded.
|
||||||
|
* @param recipientHexEncodedX25519PublicKey the X25519 public key to encrypt for. Could be the Session ID of a user, or the public key of a closed group.
|
||||||
|
*
|
||||||
|
* @return the encrypted message.
|
||||||
|
*/
|
||||||
|
fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts `envelope.content` using the Session protocol. If the envelope type is `UNIDENTIFIED_SENDER` the message is assumed to be a one-to-one
|
||||||
|
* message. If the envelope type is `CLOSED_GROUP_CIPHERTEXT` the message is assumed to be a closed group message. In the latter case `envelope.source`
|
||||||
|
* must be set to the closed group's public key.
|
||||||
|
*
|
||||||
|
* @param envelope the envelope for which to decrypt the content.
|
||||||
|
*
|
||||||
|
* @return the padded plaintext.
|
||||||
|
*/
|
||||||
|
fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String>
|
||||||
|
}
|
@ -9,15 +9,23 @@ import org.session.libsignal.service.api.crypto.SignalServiceCipher
|
|||||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||||
import org.session.libsignal.service.api.push.SignalServiceAddress
|
import org.session.libsignal.service.api.push.SignalServiceAddress
|
||||||
import org.session.libsignal.service.internal.push.PushTransportDetails
|
import org.session.libsignal.service.internal.push.PushTransportDetails
|
||||||
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
||||||
|
|
||||||
class LokiServiceCipher(
|
class LokiServiceCipher(
|
||||||
localAddress: SignalServiceAddress,
|
localAddress: SignalServiceAddress,
|
||||||
private val signalProtocolStore: SignalProtocolStore,
|
private val signalProtocolStore: SignalProtocolStore,
|
||||||
private val sskDatabase: SharedSenderKeysDatabaseProtocol,
|
private val sskDatabase: SharedSenderKeysDatabaseProtocol,
|
||||||
|
sessionProtocolImpl: SessionProtocol,
|
||||||
sessionResetProtocol: SessionResetProtocol,
|
sessionResetProtocol: SessionResetProtocol,
|
||||||
certificateValidator: CertificateValidator?)
|
certificateValidator: CertificateValidator?)
|
||||||
: SignalServiceCipher(localAddress, signalProtocolStore, sskDatabase, sessionResetProtocol, certificateValidator) {
|
: SignalServiceCipher(
|
||||||
|
localAddress,
|
||||||
|
signalProtocolStore,
|
||||||
|
sskDatabase,
|
||||||
|
sessionResetProtocol,
|
||||||
|
sessionProtocolImpl,
|
||||||
|
certificateValidator) {
|
||||||
|
|
||||||
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
|
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user