mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 13:18:26 +00:00
remove shared sender keys
This commit is contained in:
parent
568fddf91d
commit
9d0831b874
@ -75,7 +75,6 @@ import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
|||||||
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;
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
|
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
|
||||||
@ -182,12 +181,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
|
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
|
||||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||||
SharedSenderKeysDatabase sskDatabase = DatabaseFactory.getSSKDatabase(this);
|
|
||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
SessionResetImplementation sessionResetImpl = new SessionResetImplementation(this);
|
SessionResetImplementation sessionResetImpl = new SessionResetImplementation(this);
|
||||||
MessagingConfiguration.Companion.configure(this,
|
MessagingConfiguration.Companion.configure(this,
|
||||||
DatabaseFactory.getStorage(this),
|
DatabaseFactory.getStorage(this),
|
||||||
sskDatabase,
|
|
||||||
DatabaseFactory.getAttachmentProvider(this),
|
DatabaseFactory.getAttachmentProvider(this),
|
||||||
new SessionProtocolImpl(this));
|
new SessionProtocolImpl(this));
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
@ -507,8 +504,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
}
|
}
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
SharedSenderKeysDatabase sskDatabase = DatabaseFactory.getSSKDatabase(this);
|
ClosedGroupPoller.Companion.configureIfNeeded(this);
|
||||||
ClosedGroupPoller.Companion.configureIfNeeded(this, sskDatabase);
|
|
||||||
closedGroupPoller = ClosedGroupPoller.Companion.getShared();
|
closedGroupPoller = ClosedGroupPoller.Companion.getShared();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1098,16 +1098,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
builder.setPositiveButton(R.string.yes, (dialog, which) -> {
|
builder.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||||
Recipient groupRecipient = getRecipient();
|
Recipient groupRecipient = getRecipient();
|
||||||
String groupPublicKey;
|
String groupPublicKey;
|
||||||
boolean isSSKBasedClosedGroup;
|
boolean isClosedGroup;
|
||||||
try {
|
try {
|
||||||
groupPublicKey = HexEncodingKt.toHexString(GroupUtil.doubleDecodeGroupID(groupRecipient.getAddress().toString()));
|
groupPublicKey = HexEncodingKt.toHexString(GroupUtil.doubleDecodeGroupID(groupRecipient.getAddress().toString()));
|
||||||
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey);
|
isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
groupPublicKey = null;
|
groupPublicKey = null;
|
||||||
isSSKBasedClosedGroup = false;
|
isClosedGroup = false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (isSSKBasedClosedGroup) {
|
if (isClosedGroup) {
|
||||||
ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey);
|
ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey);
|
||||||
initializeEnabledCheck();
|
initializeEnabledCheck();
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,7 +34,6 @@ 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.database.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.SessionJobDatabase;
|
import org.thoughtcrime.securesms.loki.database.SessionJobDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
|
||||||
|
|
||||||
public class DatabaseFactory {
|
public class DatabaseFactory {
|
||||||
|
|
||||||
@ -67,7 +66,6 @@ public class DatabaseFactory {
|
|||||||
private final LokiThreadDatabase lokiThreadDatabase;
|
private final LokiThreadDatabase lokiThreadDatabase;
|
||||||
private final LokiUserDatabase lokiUserDatabase;
|
private final LokiUserDatabase lokiUserDatabase;
|
||||||
private final LokiBackupFilesDatabase lokiBackupFilesDatabase;
|
private final LokiBackupFilesDatabase lokiBackupFilesDatabase;
|
||||||
private final SharedSenderKeysDatabase sskDatabase;
|
|
||||||
private final SessionJobDatabase sessionJobDatabase;
|
private final SessionJobDatabase sessionJobDatabase;
|
||||||
|
|
||||||
// Refactor
|
// Refactor
|
||||||
@ -176,10 +174,6 @@ public class DatabaseFactory {
|
|||||||
return getInstance(context).lokiBackupFilesDatabase;
|
return getInstance(context).lokiBackupFilesDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SharedSenderKeysDatabase getSSKDatabase(Context context) {
|
|
||||||
return getInstance(context).sskDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SessionJobDatabase getSessionJobDatabase(Context context) {
|
public static SessionJobDatabase getSessionJobDatabase(Context context) {
|
||||||
return getInstance(context).sessionJobDatabase;
|
return getInstance(context).sessionJobDatabase;
|
||||||
}
|
}
|
||||||
@ -229,7 +223,6 @@ public class DatabaseFactory {
|
|||||||
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
|
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
|
||||||
this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper);
|
this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper);
|
||||||
this.lokiBackupFilesDatabase = new LokiBackupFilesDatabase(context, databaseHelper);
|
this.lokiBackupFilesDatabase = new LokiBackupFilesDatabase(context, databaseHelper);
|
||||||
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
|
|
||||||
this.storage = new Storage(context, databaseHelper);
|
this.storage = new Storage(context, databaseHelper);
|
||||||
this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper);
|
this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper);
|
||||||
this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper);
|
this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper);
|
||||||
|
@ -417,9 +417,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun isClosedGroup(publicKey: String): Boolean {
|
override fun isClosedGroup(publicKey: String): Boolean {
|
||||||
val isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(publicKey)
|
val isClosedGroup = DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(publicKey)
|
||||||
val address = Address.fromSerialized(publicKey)
|
val address = Address.fromSerialized(publicKey)
|
||||||
return address.isClosedGroup || isSSKBasedClosedGroup
|
return address.isClosedGroup || isClosedGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair> {
|
override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair> {
|
||||||
@ -431,7 +431,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllClosedGroupPublicKeys(): Set<String> {
|
override fun getAllClosedGroupPublicKeys(): Set<String> {
|
||||||
return DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()
|
return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addClosedGroupPublicKey(groupPublicKey: String) {
|
override fun addClosedGroupPublicKey(groupPublicKey: String) {
|
||||||
|
@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
|
|||||||
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.database.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration;
|
||||||
|
|
||||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||||
@ -131,9 +130,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
||||||
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
|
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
|
||||||
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
|
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand());
|
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateCurrentClosedGroupRatchetTableCommand());
|
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
|
|
||||||
|
|
||||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||||
@ -186,8 +182,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
if (oldVersion < lokiV12) {
|
if (oldVersion < lokiV12) {
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateCurrentClosedGroupRatchetTableCommand());
|
db.execSQL(ClosedGroupsMigration.getCreateCurrentClosedGroupRatchetTableCommand());
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
|
db.execSQL(ClosedGroupsMigration.getCreateClosedGroupPrivateKeyTableCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < lokiV13) {
|
if (oldVersion < lokiV13) {
|
||||||
@ -199,7 +195,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < lokiV15) {
|
if (oldVersion < lokiV15) {
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand());
|
db.execSQL(ClosedGroupsMigration.getCreateOldClosedGroupRatchetTableCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < lokiV16) {
|
if (oldVersion < lokiV16) {
|
||||||
|
@ -98,7 +98,6 @@ public class SignalCommunicationModule {
|
|||||||
Optional.of(new MessageSenderEventListener(context)),
|
Optional.of(new MessageSenderEventListener(context)),
|
||||||
TextSecurePreferences.getLocalNumber(context),
|
TextSecurePreferences.getLocalNumber(context),
|
||||||
DatabaseFactory.getLokiAPIDatabase(context),
|
DatabaseFactory.getLokiAPIDatabase(context),
|
||||||
DatabaseFactory.getSSKDatabase(context),
|
|
||||||
DatabaseFactory.getLokiThreadDatabase(context),
|
DatabaseFactory.getLokiThreadDatabase(context),
|
||||||
DatabaseFactory.getLokiMessageDatabase(context),
|
DatabaseFactory.getLokiMessageDatabase(context),
|
||||||
null, // DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
null, // DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
||||||
|
@ -248,7 +248,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
||||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context);
|
||||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator());
|
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator());
|
||||||
|
|
||||||
SignalServiceContent content = cipher.decrypt(envelope);
|
SignalServiceContent content = cipher.decrypt(envelope);
|
||||||
|
|
||||||
|
@ -246,21 +246,21 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
val admins = members.toSet() //TODO For now, consider all the users to be admins.
|
val admins = members.toSet() //TODO For now, consider all the users to be admins.
|
||||||
|
|
||||||
var isSSKBasedClosedGroup: Boolean
|
var isClosedGroup: Boolean
|
||||||
var groupPublicKey: String?
|
var groupPublicKey: String?
|
||||||
try {
|
try {
|
||||||
groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
|
groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
|
||||||
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey)
|
isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
groupPublicKey = null
|
groupPublicKey = null
|
||||||
isSSKBasedClosedGroup = false
|
isClosedGroup = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (members.isEmpty()) {
|
if (members.isEmpty()) {
|
||||||
return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
|
return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocolV2.groupSizeLimit else legacyGroupSizeLimit
|
val maxGroupMembers = if (isClosedGroup) ClosedGroupsProtocolV2.groupSizeLimit else legacyGroupSizeLimit
|
||||||
if (members.size >= maxGroupMembers) {
|
if (members.size >= maxGroupMembers) {
|
||||||
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
|
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
@ -273,7 +273,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
|
return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSSKBasedClosedGroup) {
|
if (isClosedGroup) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
loaderContainer.fadeIn()
|
loaderContainer.fadeIn()
|
||||||
val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
|
val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
|
||||||
|
@ -167,7 +167,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
|
||||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
||||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||||
val sskDatabase = DatabaseFactory.getSSKDatabase(this)
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
val sessionResetImpl = SessionResetImplementation(this)
|
val sessionResetImpl = SessionResetImplementation(this)
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
@ -315,7 +314,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
val threadID = thread.threadId
|
val threadID = thread.threadId
|
||||||
val recipient = thread.recipient
|
val recipient = thread.recipient
|
||||||
val threadDB = DatabaseFactory.getThreadDatabase(this)
|
val threadDB = DatabaseFactory.getThreadDatabase(this)
|
||||||
val isClosedGroup = recipient.address.isClosedGroup
|
|
||||||
val dialogMessage: String
|
val dialogMessage: String
|
||||||
if (recipient.isGroupRecipient) {
|
if (recipient.isGroupRecipient) {
|
||||||
val group = DatabaseFactory.getGroupDatabase(this).getGroup(recipient.address.toString()).orNull()
|
val group = DatabaseFactory.getGroupDatabase(this).getGroup(recipient.address.toString()).orNull()
|
||||||
@ -333,17 +331,17 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
val context = this@HomeActivity as Context
|
val context = this@HomeActivity as Context
|
||||||
|
|
||||||
// Send a leave group message if this is an active closed group
|
// Send a leave group message if this is an active closed group
|
||||||
if (isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) {
|
if (recipient.address.isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) {
|
||||||
var isSSKBasedClosedGroup: Boolean
|
var isClosedGroup: Boolean
|
||||||
var groupPublicKey: String?
|
var groupPublicKey: String?
|
||||||
try {
|
try {
|
||||||
groupPublicKey = GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString()
|
groupPublicKey = GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString()
|
||||||
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(groupPublicKey)
|
isClosedGroup = DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(groupPublicKey)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
groupPublicKey = null
|
groupPublicKey = null
|
||||||
isSSKBasedClosedGroup = false
|
isClosedGroup = false
|
||||||
}
|
}
|
||||||
if (isSSKBasedClosedGroup) {
|
if (isClosedGroup) {
|
||||||
ClosedGroupsProtocolV2.explicitLeave(context, groupPublicKey!!)
|
ClosedGroupsProtocolV2.explicitLeave(context, groupPublicKey!!)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
|
||||||
|
@ -76,8 +76,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
|
|||||||
promises.add(privateChatsPromise)
|
promises.add(privateChatsPromise)
|
||||||
|
|
||||||
// Closed groups
|
// Closed groups
|
||||||
val sskDatabase = DatabaseFactory.getSSKDatabase(context)
|
ClosedGroupPoller.configureIfNeeded(context)
|
||||||
ClosedGroupPoller.configureIfNeeded(context, sskDatabase)
|
|
||||||
promises.addAll(ClosedGroupPoller.shared.pollOnce())
|
promises.addAll(ClosedGroupPoller.shared.pollOnce())
|
||||||
|
|
||||||
// Open Groups
|
// Open Groups
|
||||||
|
@ -7,14 +7,14 @@ import nl.komponents.kovenant.functional.bind
|
|||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase
|
|
||||||
import org.session.libsignal.utilities.successBackground
|
import org.session.libsignal.utilities.successBackground
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||||
import org.session.libsignal.service.loki.api.SnodeAPI
|
import org.session.libsignal.service.loki.api.SnodeAPI
|
||||||
import org.session.libsignal.service.loki.api.SwarmAPI
|
import org.session.libsignal.service.loki.api.SwarmAPI
|
||||||
import org.session.libsignal.service.loki.utilities.getRandomElementOrNull
|
import org.session.libsignal.service.loki.utilities.getRandomElementOrNull
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
|
||||||
class ClosedGroupPoller private constructor(private val context: Context, private val database: SharedSenderKeysDatabase) {
|
class ClosedGroupPoller private constructor(private val context: Context) {
|
||||||
private var isPolling = false
|
private var isPolling = false
|
||||||
private val handler: Handler by lazy { Handler() }
|
private val handler: Handler by lazy { Handler() }
|
||||||
|
|
||||||
@ -32,9 +32,9 @@ class ClosedGroupPoller private constructor(private val context: Context, privat
|
|||||||
|
|
||||||
public lateinit var shared: ClosedGroupPoller
|
public lateinit var shared: ClosedGroupPoller
|
||||||
|
|
||||||
public fun configureIfNeeded(context: Context, sskDatabase: SharedSenderKeysDatabase) {
|
public fun configureIfNeeded(context: Context) {
|
||||||
if (::shared.isInitialized) { return; }
|
if (::shared.isInitialized) { return; }
|
||||||
shared = ClosedGroupPoller(context, sskDatabase)
|
shared = ClosedGroupPoller(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
@ -66,7 +66,7 @@ class ClosedGroupPoller private constructor(private val context: Context, privat
|
|||||||
// region Private API
|
// region Private API
|
||||||
private fun poll(): List<Promise<Unit, Exception>> {
|
private fun poll(): List<Promise<Unit, Exception>> {
|
||||||
if (!isPolling) { return listOf() }
|
if (!isPolling) { return listOf() }
|
||||||
val publicKeys = database.getAllClosedGroupPublicKeys()
|
val publicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
|
||||||
return publicKeys.map { publicKey ->
|
return publicKeys.map { publicKey ->
|
||||||
val promise = SwarmAPI.shared.getSwarm(publicKey).bind { swarm ->
|
val promise = SwarmAPI.shared.getSwarm(publicKey).bind { swarm ->
|
||||||
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||||
|
@ -53,7 +53,7 @@ object LokiPushNotificationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unsubscribe from all closed groups
|
// Unsubscribe from all closed groups
|
||||||
val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()
|
val allClosedGroupPublicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
allClosedGroupPublicKeys.forEach { closedGroup ->
|
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||||
performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey)
|
performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey)
|
||||||
@ -84,7 +84,7 @@ object LokiPushNotificationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Subscribe to all closed groups
|
// Subscribe to all closed groups
|
||||||
val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()
|
val allClosedGroupPublicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
|
||||||
allClosedGroupPublicKeys.forEach { closedGroup ->
|
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||||
performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey)
|
performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import org.session.libsignal.service.loki.utilities.toHexString
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
|
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
|
||||||
@ -454,6 +455,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun isClosedGroup(groupPublicKey: String): Boolean {
|
||||||
|
if (!PublicKeyValidation.isValid(groupPublicKey)) { return false }
|
||||||
|
return getAllClosedGroupPublicKeys().contains(groupPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
fun removeClosedGroupPublicKey(groupPublicKey: String) {
|
fun removeClosedGroupPublicKey(groupPublicKey: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey))
|
database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey))
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.database
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
|
||||||
import org.thoughtcrime.securesms.database.Database
|
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchet
|
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType
|
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey
|
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
|
||||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
|
|
||||||
class SharedSenderKeysDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), SharedSenderKeysDatabaseProtocol {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// Shared
|
|
||||||
public val closedGroupPublicKey = "closed_group_public_key"
|
|
||||||
// Ratchets
|
|
||||||
private val oldClosedGroupRatchetTable = "old_closed_group_ratchet_table"
|
|
||||||
private val currentClosedGroupRatchetTable = "closed_group_ratchet_table"
|
|
||||||
private val senderPublicKey = "sender_public_key"
|
|
||||||
private val chainKey = "chain_key"
|
|
||||||
private val keyIndex = "key_index"
|
|
||||||
private val messageKeys = "message_keys"
|
|
||||||
@JvmStatic val createOldClosedGroupRatchetTableCommand
|
|
||||||
= "CREATE TABLE $oldClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " +
|
|
||||||
"$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));"
|
|
||||||
// Private keys
|
|
||||||
@JvmStatic val createCurrentClosedGroupRatchetTableCommand
|
|
||||||
= "CREATE TABLE $currentClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " +
|
|
||||||
"$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));"
|
|
||||||
// Private keys
|
|
||||||
public val closedGroupPrivateKeyTable = "closed_group_private_key_table"
|
|
||||||
public val closedGroupPrivateKey = "closed_group_private_key"
|
|
||||||
@JvmStatic val createClosedGroupPrivateKeyTableCommand
|
|
||||||
= "CREATE TABLE $closedGroupPrivateKeyTable ($closedGroupPublicKey STRING PRIMARY KEY, $closedGroupPrivateKey STRING);"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTable(collection: ClosedGroupRatchetCollectionType): String {
|
|
||||||
return when (collection) {
|
|
||||||
ClosedGroupRatchetCollectionType.Old -> oldClosedGroupRatchetTable
|
|
||||||
ClosedGroupRatchetCollectionType.Current -> currentClosedGroupRatchetTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Ratchets & Sender Keys
|
|
||||||
override fun getClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, collection: ClosedGroupRatchetCollectionType): ClosedGroupRatchet? {
|
|
||||||
val database = databaseHelper.readableDatabase
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ? AND ${Companion.senderPublicKey} = ?"
|
|
||||||
return database.get(getTable(collection), query, arrayOf( groupPublicKey, senderPublicKey )) { cursor ->
|
|
||||||
val chainKey = cursor.getString(Companion.chainKey)
|
|
||||||
val keyIndex = cursor.getInt(Companion.keyIndex)
|
|
||||||
val messageKeys = cursor.getString(Companion.messageKeys).split("-")
|
|
||||||
ClosedGroupRatchet(chainKey, keyIndex, messageKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, collection: ClosedGroupRatchetCollectionType) {
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
val values = ContentValues()
|
|
||||||
values.put(Companion.closedGroupPublicKey, groupPublicKey)
|
|
||||||
values.put(Companion.senderPublicKey, senderPublicKey)
|
|
||||||
values.put(Companion.chainKey, ratchet.chainKey)
|
|
||||||
values.put(Companion.keyIndex, ratchet.keyIndex)
|
|
||||||
values.put(Companion.messageKeys, ratchet.messageKeys.joinToString("-"))
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ? AND ${Companion.senderPublicKey} = ?"
|
|
||||||
database.insertOrUpdate(getTable(collection), values, query, arrayOf( groupPublicKey, senderPublicKey ))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType) {
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ?"
|
|
||||||
database.delete(getTable(collection), query, arrayOf( groupPublicKey ))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set<Pair<String, ClosedGroupRatchet>> {
|
|
||||||
val database = databaseHelper.readableDatabase
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ?"
|
|
||||||
return database.getAll(getTable(collection), query, arrayOf( groupPublicKey )) { cursor ->
|
|
||||||
val chainKey = cursor.getString(Companion.chainKey)
|
|
||||||
val keyIndex = cursor.getInt(Companion.keyIndex)
|
|
||||||
val messageKeys = cursor.getString(Companion.messageKeys).split("-")
|
|
||||||
val senderPublicKey = cursor.getString(Companion.senderPublicKey)
|
|
||||||
val ratchet = ClosedGroupRatchet(chainKey, keyIndex, messageKeys)
|
|
||||||
Pair(senderPublicKey, ratchet)
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAllClosedGroupSenderKeys(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set<ClosedGroupSenderKey> {
|
|
||||||
return getAllClosedGroupRatchets(groupPublicKey, collection).map { pair ->
|
|
||||||
val senderPublicKey = pair.first
|
|
||||||
val ratchet = pair.second
|
|
||||||
ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(senderPublicKey))
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Public & Private Keys
|
|
||||||
override fun getClosedGroupPrivateKey(groupPublicKey: String): String? {
|
|
||||||
val database = databaseHelper.readableDatabase
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ?"
|
|
||||||
return database.get(closedGroupPrivateKeyTable, query, arrayOf( groupPublicKey )) { cursor ->
|
|
||||||
cursor.getString(Companion.closedGroupPrivateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setClosedGroupPrivateKey(groupPublicKey: String, groupPrivateKey: String) {
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
val values = ContentValues()
|
|
||||||
values.put(Companion.closedGroupPublicKey, groupPublicKey)
|
|
||||||
values.put(Companion.closedGroupPrivateKey, groupPrivateKey)
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ?"
|
|
||||||
database.insertOrUpdate(closedGroupPrivateKeyTable, values, query, arrayOf( groupPublicKey ))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeClosedGroupPrivateKey(groupPublicKey: String) {
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
val query = "${Companion.closedGroupPublicKey} = ?"
|
|
||||||
database.delete(closedGroupPrivateKeyTable, query, arrayOf( groupPublicKey ))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAllClosedGroupPublicKeys(): Set<String> {
|
|
||||||
val database = databaseHelper.readableDatabase
|
|
||||||
val result = mutableSetOf<String>()
|
|
||||||
result.addAll(database.getAll(closedGroupPrivateKeyTable, null, null) { cursor ->
|
|
||||||
cursor.getString(Companion.closedGroupPublicKey)
|
|
||||||
}.filter {
|
|
||||||
PublicKeyValidation.isValid(it)
|
|
||||||
})
|
|
||||||
result.addAll(DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys())
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
override fun isSSKBasedClosedGroup(groupPublicKey: String): Boolean {
|
|
||||||
if (!PublicKeyValidation.isValid(groupPublicKey)) { return false }
|
|
||||||
return getAllClosedGroupPublicKeys().contains(groupPublicKey)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki.protocol
|
|||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
|
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.get
|
import org.thoughtcrime.securesms.loki.utilities.get
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getAll
|
import org.thoughtcrime.securesms.loki.utilities.getAll
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||||
@ -18,17 +17,39 @@ import java.util.*
|
|||||||
|
|
||||||
object ClosedGroupsMigration {
|
object ClosedGroupsMigration {
|
||||||
|
|
||||||
fun perform(database: net.sqlcipher.database.SQLiteDatabase) {
|
public val closedGroupPublicKey = "closed_group_public_key"
|
||||||
val publicKeys = database.getAll(SharedSenderKeysDatabase.closedGroupPrivateKeyTable, null, null) { cursor ->
|
// Ratchets
|
||||||
cursor.getString(SharedSenderKeysDatabase.closedGroupPublicKey)
|
private val oldClosedGroupRatchetTable = "old_closed_group_ratchet_table"
|
||||||
|
private val currentClosedGroupRatchetTable = "closed_group_ratchet_table"
|
||||||
|
private val senderPublicKey = "sender_public_key"
|
||||||
|
private val chainKey = "chain_key"
|
||||||
|
private val keyIndex = "key_index"
|
||||||
|
private val messageKeys = "message_keys"
|
||||||
|
@JvmStatic val createOldClosedGroupRatchetTableCommand
|
||||||
|
= "CREATE TABLE $oldClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " +
|
||||||
|
"$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));"
|
||||||
|
// Private keys
|
||||||
|
@JvmStatic val createCurrentClosedGroupRatchetTableCommand
|
||||||
|
= "CREATE TABLE $currentClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " +
|
||||||
|
"$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));"
|
||||||
|
// Private keys
|
||||||
|
public val closedGroupPrivateKeyTable = "closed_group_private_key_table"
|
||||||
|
public val closedGroupPrivateKey = "closed_group_private_key"
|
||||||
|
@JvmStatic val createClosedGroupPrivateKeyTableCommand
|
||||||
|
= "CREATE TABLE $closedGroupPrivateKeyTable ($closedGroupPublicKey STRING PRIMARY KEY, $closedGroupPrivateKey STRING);"
|
||||||
|
|
||||||
|
|
||||||
|
fun perform(database: net.sqlcipher.database.SQLiteDatabase) {
|
||||||
|
val publicKeys = database.getAll(closedGroupPrivateKeyTable, null, null) { cursor ->
|
||||||
|
cursor.getString(closedGroupPublicKey)
|
||||||
}.filter {
|
}.filter {
|
||||||
PublicKeyValidation.isValid(it)
|
PublicKeyValidation.isValid(it)
|
||||||
}
|
}
|
||||||
val keyPairs = mutableListOf<ECKeyPair>()
|
val keyPairs = mutableListOf<ECKeyPair>()
|
||||||
for (publicKey in publicKeys) {
|
for (publicKey in publicKeys) {
|
||||||
val query = "${SharedSenderKeysDatabase.closedGroupPublicKey} = ?"
|
val query = "${closedGroupPublicKey} = ?"
|
||||||
val privateKey = database.get(SharedSenderKeysDatabase.closedGroupPrivateKeyTable, query, arrayOf( publicKey )) { cursor ->
|
val privateKey = database.get(closedGroupPrivateKeyTable, query, arrayOf( publicKey )) { cursor ->
|
||||||
cursor.getString(SharedSenderKeysDatabase.closedGroupPrivateKey)
|
cursor.getString(closedGroupPrivateKey)
|
||||||
}
|
}
|
||||||
val keyPair = ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(publicKey.removing05PrefixIfNeeded())), DjbECPrivateKey(Hex.fromStringCondensed(privateKey)))
|
val keyPair = ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(publicKey.removing05PrefixIfNeeded())), DjbECPrivateKey(Hex.fromStringCondensed(privateKey)))
|
||||||
keyPairs.add(keyPair)
|
keyPairs.add(keyPair)
|
||||||
|
@ -2,16 +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.libsignal.loki.SessionResetProtocol
|
|
||||||
import org.session.libsignal.libsignal.state.*
|
|
||||||
import org.session.libsignal.metadata.certificate.CertificateValidator
|
|
||||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
|
||||||
|
|
||||||
class MessagingConfiguration(
|
class MessagingConfiguration(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val storage: StorageProtocol,
|
val storage: StorageProtocol,
|
||||||
val sskDatabase: SharedSenderKeysDatabaseProtocol,
|
|
||||||
val messageDataProvider: MessageDataProvider,
|
val messageDataProvider: MessageDataProvider,
|
||||||
val sessionProtocol: SessionProtocol)
|
val sessionProtocol: SessionProtocol)
|
||||||
{
|
{
|
||||||
@ -20,12 +15,11 @@ class MessagingConfiguration(
|
|||||||
|
|
||||||
fun configure(context: Context,
|
fun configure(context: Context,
|
||||||
storage: StorageProtocol,
|
storage: StorageProtocol,
|
||||||
sskDatabase: SharedSenderKeysDatabaseProtocol,
|
|
||||||
messageDataProvider: MessageDataProvider,
|
messageDataProvider: MessageDataProvider,
|
||||||
sessionProtocol: SessionProtocol
|
sessionProtocol: SessionProtocol
|
||||||
) {
|
) {
|
||||||
if (Companion::shared.isInitialized) { return }
|
if (Companion::shared.isInitialized) { return }
|
||||||
shared = MessagingConfiguration(context, storage, sskDatabase, messageDataProvider, sessionProtocol)
|
shared = MessagingConfiguration(context, storage, messageDataProvider, sessionProtocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -47,7 +47,7 @@ object PushNotificationAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unsubscribe from all closed groups
|
// Unsubscribe from all closed groups
|
||||||
val allClosedGroupPublicKeys = MessagingConfiguration.shared.sskDatabase.getAllClosedGroupPublicKeys()
|
val allClosedGroupPublicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||||
val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey()!!
|
val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey()!!
|
||||||
allClosedGroupPublicKeys.forEach { closedGroup ->
|
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||||
performOperation(ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey)
|
performOperation(ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey)
|
||||||
@ -77,7 +77,7 @@ object PushNotificationAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Subscribe to all closed groups
|
// Subscribe to all closed groups
|
||||||
val allClosedGroupPublicKeys = MessagingConfiguration.shared.sskDatabase.getAllClosedGroupPublicKeys()
|
val allClosedGroupPublicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||||
allClosedGroupPublicKeys.forEach { closedGroup ->
|
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||||
performOperation(ClosedGroupOperation.Subscribe, closedGroup, publicKey)
|
performOperation(ClosedGroupOperation.Subscribe, closedGroup, publicKey)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ class ClosedGroupPoller {
|
|||||||
// region Private API
|
// region Private API
|
||||||
private fun poll(): List<Promise<Unit, Exception>> {
|
private fun poll(): List<Promise<Unit, Exception>> {
|
||||||
if (!isPolling) { return listOf() }
|
if (!isPolling) { return listOf() }
|
||||||
val publicKeys = MessagingConfiguration.shared.sskDatabase.getAllClosedGroupPublicKeys()
|
val publicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||||
return publicKeys.map { publicKey ->
|
return publicKeys.map { publicKey ->
|
||||||
val promise = SnodeAPI.getSwarm(publicKey).bind { swarm ->
|
val promise = SnodeAPI.getSwarm(publicKey).bind { swarm ->
|
||||||
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||||
|
@ -75,7 +75,6 @@ import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol
|
|||||||
import org.session.libsignal.service.loki.database.LokiPreKeyBundleDatabaseProtocol;
|
import org.session.libsignal.service.loki.database.LokiPreKeyBundleDatabaseProtocol;
|
||||||
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol;
|
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol;
|
||||||
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol;
|
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol;
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
|
|
||||||
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
|
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
|
||||||
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||||
import org.session.libsignal.service.loki.utilities.Broadcaster;
|
import org.session.libsignal.service.loki.utilities.Broadcaster;
|
||||||
@ -121,7 +120,6 @@ public class SignalServiceMessageSender {
|
|||||||
// Loki
|
// Loki
|
||||||
private final String userPublicKey;
|
private final String userPublicKey;
|
||||||
private final LokiAPIDatabaseProtocol apiDatabase;
|
private final LokiAPIDatabaseProtocol apiDatabase;
|
||||||
private final SharedSenderKeysDatabaseProtocol sskDatabase;
|
|
||||||
private final LokiThreadDatabaseProtocol threadDatabase;
|
private final LokiThreadDatabaseProtocol threadDatabase;
|
||||||
private final LokiMessageDatabaseProtocol messageDatabase;
|
private final LokiMessageDatabaseProtocol messageDatabase;
|
||||||
private final LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase;
|
private final LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase;
|
||||||
@ -151,7 +149,6 @@ public class SignalServiceMessageSender {
|
|||||||
Optional<EventListener> eventListener,
|
Optional<EventListener> eventListener,
|
||||||
String userPublicKey,
|
String userPublicKey,
|
||||||
LokiAPIDatabaseProtocol apiDatabase,
|
LokiAPIDatabaseProtocol apiDatabase,
|
||||||
SharedSenderKeysDatabaseProtocol sskDatabase,
|
|
||||||
LokiThreadDatabaseProtocol threadDatabase,
|
LokiThreadDatabaseProtocol threadDatabase,
|
||||||
LokiMessageDatabaseProtocol messageDatabase,
|
LokiMessageDatabaseProtocol messageDatabase,
|
||||||
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
||||||
@ -161,7 +158,7 @@ public class SignalServiceMessageSender {
|
|||||||
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, sessionProtocolImpl, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster);
|
this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionProtocolImpl, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
||||||
@ -174,7 +171,6 @@ public class SignalServiceMessageSender {
|
|||||||
Optional<EventListener> eventListener,
|
Optional<EventListener> eventListener,
|
||||||
String userPublicKey,
|
String userPublicKey,
|
||||||
LokiAPIDatabaseProtocol apiDatabase,
|
LokiAPIDatabaseProtocol apiDatabase,
|
||||||
SharedSenderKeysDatabaseProtocol sskDatabase,
|
|
||||||
LokiThreadDatabaseProtocol threadDatabase,
|
LokiThreadDatabaseProtocol threadDatabase,
|
||||||
LokiMessageDatabaseProtocol messageDatabase,
|
LokiMessageDatabaseProtocol messageDatabase,
|
||||||
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
||||||
@ -193,7 +189,6 @@ public class SignalServiceMessageSender {
|
|||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
this.userPublicKey = userPublicKey;
|
this.userPublicKey = userPublicKey;
|
||||||
this.apiDatabase = apiDatabase;
|
this.apiDatabase = apiDatabase;
|
||||||
this.sskDatabase = sskDatabase;
|
|
||||||
this.threadDatabase = threadDatabase;
|
this.threadDatabase = threadDatabase;
|
||||||
this.messageDatabase = messageDatabase;
|
this.messageDatabase = messageDatabase;
|
||||||
this.preKeyBundleDatabase = preKeyBundleDatabase;
|
this.preKeyBundleDatabase = preKeyBundleDatabase;
|
||||||
@ -1181,9 +1176,9 @@ public class SignalServiceMessageSender {
|
|||||||
|
|
||||||
PushTransportDetails transportDetails = new PushTransportDetails(3);
|
PushTransportDetails transportDetails = new PushTransportDetails(3);
|
||||||
String publicKey = recipient.getNumber(); // Could be a contact's public key or the public key of a SSK group
|
String publicKey = recipient.getNumber(); // Could be a contact's public key or the public key of a SSK group
|
||||||
boolean isSSKBasedClosedGroup = sskDatabase.isSSKBasedClosedGroup(publicKey);
|
boolean isClosedGroup = apiDatabase.isClosedGroup(publicKey);
|
||||||
String encryptionPublicKey;
|
String encryptionPublicKey;
|
||||||
if (isSSKBasedClosedGroup) {
|
if (isClosedGroup) {
|
||||||
ECKeyPair encryptionKeyPair = apiDatabase.getLatestClosedGroupEncryptionKeyPair(publicKey);
|
ECKeyPair encryptionKeyPair = apiDatabase.getLatestClosedGroupEncryptionKeyPair(publicKey);
|
||||||
encryptionPublicKey = HexEncodingKt.getHexEncodedPublicKey(encryptionKeyPair);
|
encryptionPublicKey = HexEncodingKt.getHexEncodedPublicKey(encryptionKeyPair);
|
||||||
} else {
|
} else {
|
||||||
@ -1191,7 +1186,7 @@ public class SignalServiceMessageSender {
|
|||||||
}
|
}
|
||||||
byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), encryptionPublicKey);
|
byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), encryptionPublicKey);
|
||||||
String body = Base64.encodeBytes(ciphertext);
|
String body = Base64.encodeBytes(ciphertext);
|
||||||
int type = isSSKBasedClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
int type = isClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
||||||
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||||
OutgoingPushMessage message = new OutgoingPushMessage(type, 1, 0, body);
|
OutgoingPushMessage message = new OutgoingPushMessage(type, 1, 0, body);
|
||||||
messages.add(message);
|
messages.add(message);
|
||||||
|
@ -88,8 +88,6 @@ import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
|||||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
||||||
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.sessionmanagement.PreKeyBundleMessage;
|
import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -115,7 +113,6 @@ public class SignalServiceCipher {
|
|||||||
|
|
||||||
private final SignalProtocolStore signalProtocolStore;
|
private final SignalProtocolStore signalProtocolStore;
|
||||||
private final SessionResetProtocol sessionResetProtocol;
|
private final SessionResetProtocol sessionResetProtocol;
|
||||||
private final SharedSenderKeysDatabaseProtocol sskDatabase;
|
|
||||||
private final SignalServiceAddress localAddress;
|
private final SignalServiceAddress localAddress;
|
||||||
private final SessionProtocol sessionProtocolImpl;
|
private final SessionProtocol sessionProtocolImpl;
|
||||||
private final LokiAPIDatabaseProtocol apiDB;
|
private final LokiAPIDatabaseProtocol apiDB;
|
||||||
@ -123,7 +120,6 @@ public class SignalServiceCipher {
|
|||||||
|
|
||||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||||
SignalProtocolStore signalProtocolStore,
|
SignalProtocolStore signalProtocolStore,
|
||||||
SharedSenderKeysDatabaseProtocol sskDatabase,
|
|
||||||
SessionResetProtocol sessionResetProtocol,
|
SessionResetProtocol sessionResetProtocol,
|
||||||
SessionProtocol sessionProtocolImpl,
|
SessionProtocol sessionProtocolImpl,
|
||||||
LokiAPIDatabaseProtocol apiDB,
|
LokiAPIDatabaseProtocol apiDB,
|
||||||
@ -131,56 +127,55 @@ public class SignalServiceCipher {
|
|||||||
{
|
{
|
||||||
this.signalProtocolStore = signalProtocolStore;
|
this.signalProtocolStore = signalProtocolStore;
|
||||||
this.sessionResetProtocol = sessionResetProtocol;
|
this.sessionResetProtocol = sessionResetProtocol;
|
||||||
this.sskDatabase = sskDatabase;
|
|
||||||
this.localAddress = localAddress;
|
this.localAddress = localAddress;
|
||||||
this.sessionProtocolImpl = sessionProtocolImpl;
|
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||||
this.apiDB = apiDB;
|
this.apiDB = apiDB;
|
||||||
this.certificateValidator = certificateValidator;
|
this.certificateValidator = certificateValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutgoingPushMessage encrypt(SignalProtocolAddress destination,
|
// public OutgoingPushMessage encrypt(SignalProtocolAddress destination,
|
||||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
// Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||||
byte[] unpaddedMessage)
|
// byte[] unpaddedMessage)
|
||||||
throws UntrustedIdentityException, InvalidKeyException, IOException
|
// throws UntrustedIdentityException, InvalidKeyException, IOException
|
||||||
{
|
// {
|
||||||
if (unidentifiedAccess.isPresent() && sskDatabase.isSSKBasedClosedGroup(destination.getName())) {
|
// if (unidentifiedAccess.isPresent() && sskDatabase.isSSKBasedClosedGroup(destination.getName())) {
|
||||||
String userPublicKey = localAddress.getNumber();
|
// String userPublicKey = localAddress.getNumber();
|
||||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(userPublicKey, 1);
|
// SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(userPublicKey, 1);
|
||||||
SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, signalProtocolAddress);
|
// SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, signalProtocolAddress);
|
||||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
|
// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
|
||||||
byte[] plaintext = transportDetails.getPaddedMessageBody(unpaddedMessage);
|
// byte[] plaintext = transportDetails.getPaddedMessageBody(unpaddedMessage);
|
||||||
byte[] ciphertext = ClosedGroupUtilities.encrypt(plaintext, destination.getName(), userPublicKey);
|
// byte[] ciphertext = ClosedGroupUtilities.encrypt(plaintext, destination.getName(), userPublicKey);
|
||||||
String body = Base64.encodeBytes(ciphertext);
|
// String body = Base64.encodeBytes(ciphertext);
|
||||||
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
|
// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
|
||||||
return new OutgoingPushMessage(Type.CLOSED_GROUP_CIPHERTEXT_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
|
// return new OutgoingPushMessage(Type.CLOSED_GROUP_CIPHERTEXT_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
|
||||||
} else if (unidentifiedAccess.isPresent()) {
|
// } else if (unidentifiedAccess.isPresent()) {
|
||||||
SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1));
|
// SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1));
|
||||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
|
// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
|
||||||
byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage));
|
// byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage));
|
||||||
String body = Base64.encodeBytes(ciphertext);
|
// String body = Base64.encodeBytes(ciphertext);
|
||||||
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
|
// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
|
||||||
|
//
|
||||||
return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
|
// return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
|
||||||
} else {
|
// } else {
|
||||||
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination);
|
// SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination);
|
||||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||||
CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
// CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
||||||
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId();
|
// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId();
|
||||||
String body = Base64.encodeBytes(message.serialize());
|
// String body = Base64.encodeBytes(message.serialize());
|
||||||
|
//
|
||||||
int type;
|
// int type;
|
||||||
|
//
|
||||||
switch (message.getType()) {
|
// switch (message.getType()) {
|
||||||
case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break;
|
// case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break;
|
||||||
case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break;
|
// case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break;
|
||||||
case CiphertextMessage.FALLBACK_MESSAGE_TYPE: type = Type.FALLBACK_MESSAGE_VALUE; break;
|
// case CiphertextMessage.FALLBACK_MESSAGE_TYPE: type = Type.FALLBACK_MESSAGE_VALUE; break;
|
||||||
case CiphertextMessage.CLOSED_GROUP_CIPHERTEXT: type = Type.CLOSED_GROUP_CIPHERTEXT_VALUE; break;
|
// case CiphertextMessage.CLOSED_GROUP_CIPHERTEXT: type = Type.CLOSED_GROUP_CIPHERTEXT_VALUE; break;
|
||||||
default: throw new AssertionError("Bad type: " + message.getType());
|
// default: throw new AssertionError("Bad type: " + message.getType());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body);
|
// return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a received {@link SignalServiceEnvelope}
|
* Decrypt a received {@link SignalServiceEnvelope}
|
||||||
|
@ -11,9 +11,8 @@ 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.api.crypto.SessionProtocol
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
|
||||||
|
|
||||||
class LokiServiceCipher(localAddress: SignalServiceAddress, private val signalProtocolStore: SignalProtocolStore, private val sskDatabase: SharedSenderKeysDatabaseProtocol, sessionProtocolImpl: SessionProtocol, sessionResetProtocol: SessionResetProtocol, apiDB: LokiAPIDatabaseProtocol, certificateValidator: CertificateValidator?) : SignalServiceCipher(localAddress, signalProtocolStore, sskDatabase, sessionResetProtocol, sessionProtocolImpl, apiDB, certificateValidator) {
|
class LokiServiceCipher(localAddress: SignalServiceAddress, private val signalProtocolStore: SignalProtocolStore, sessionProtocolImpl: SessionProtocol, sessionResetProtocol: SessionResetProtocol, apiDB: LokiAPIDatabaseProtocol, certificateValidator: CertificateValidator?) : SignalServiceCipher(localAddress, signalProtocolStore, sessionResetProtocol, sessionProtocolImpl, apiDB, certificateValidator) {
|
||||||
|
|
||||||
|
|
||||||
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
|
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
|
||||||
|
@ -37,4 +37,5 @@ interface LokiAPIDatabaseProtocol {
|
|||||||
fun getUserX25519KeyPair(): ECKeyPair
|
fun getUserX25519KeyPair(): ECKeyPair
|
||||||
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): List<ECKeyPair>
|
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): List<ECKeyPair>
|
||||||
fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair?
|
fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair?
|
||||||
|
fun isClosedGroup(groupPublicKey: String): Boolean
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.closedgroups
|
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.utilities.prettifiedDescription
|
|
||||||
|
|
||||||
public class ClosedGroupRatchet(public val chainKey: String, public val keyIndex: Int, public val messageKeys: List<String>) {
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return if (other is ClosedGroupRatchet) {
|
|
||||||
chainKey == other.chainKey && keyIndex == other.keyIndex && messageKeys == other.messageKeys
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return chainKey.hashCode() xor keyIndex.hashCode() xor messageKeys.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "[ chainKey : $chainKey, keyIndex : $keyIndex, messageKeys : ${messageKeys.prettifiedDescription()} ]"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.closedgroups
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
|
||||||
|
|
||||||
public class ClosedGroupSenderKey(public val chainKey: ByteArray, public val keyIndex: Int, public val publicKey: ByteArray) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
public fun fromJSON(jsonAsString: String): ClosedGroupSenderKey? {
|
|
||||||
try {
|
|
||||||
val json = JsonUtil.fromJson(jsonAsString, Map::class.java)
|
|
||||||
val chainKey = Hex.fromStringCondensed(json["chainKey"] as String)
|
|
||||||
val keyIndex = json["keyIndex"] as Int
|
|
||||||
val publicKey = Hex.fromStringCondensed(json["publicKey"] as String)
|
|
||||||
return ClosedGroupSenderKey(chainKey, keyIndex, publicKey)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.d("Loki", "Couldn't parse closed group sender key from: $jsonAsString.")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun toJSON(): String {
|
|
||||||
val json = mapOf( "chainKey" to chainKey.toHexString(), "keyIndex" to keyIndex, "publicKey" to publicKey.toHexString() )
|
|
||||||
return JsonUtil.toJson(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun toProto(): SignalServiceProtos.ClosedGroupUpdate.SenderKey {
|
|
||||||
val builder = SignalServiceProtos.ClosedGroupUpdate.SenderKey.newBuilder()
|
|
||||||
builder.chainKey = ByteString.copyFrom(chainKey)
|
|
||||||
builder.keyIndex = keyIndex
|
|
||||||
builder.publicKey = ByteString.copyFrom(publicKey)
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return if (other is ClosedGroupSenderKey) {
|
|
||||||
chainKey.contentEquals(other.chainKey) && keyIndex == other.keyIndex && publicKey.contentEquals(other.publicKey)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return chainKey.hashCode() xor keyIndex.hashCode() xor publicKey.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "[ chainKey : ${chainKey.toHexString()}, keyIndex : $keyIndex, messageKeys : ${publicKey.toHexString()} ]"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.closedgroups
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.whispersystems.curve25519.Curve25519
|
|
||||||
import org.session.libsignal.libsignal.loki.ClosedGroupCiphertextMessage
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.libsignal.util.Pair
|
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
|
||||||
import org.session.libsignal.service.loki.api.utilities.DecryptionUtilities
|
|
||||||
import org.session.libsignal.service.loki.api.utilities.EncryptionUtilities
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
|
||||||
import javax.crypto.Mac
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
public object ClosedGroupUtilities {
|
|
||||||
|
|
||||||
sealed class Error(val description: String) : Exception() {
|
|
||||||
object InvalidGroupPublicKey : Error("Invalid group public key.")
|
|
||||||
object NoData : Error("Received an empty envelope.")
|
|
||||||
object NoGroupPrivateKey : Error("Missing group private key.")
|
|
||||||
object ParsingFailed : Error("Couldn't parse closed group ciphertext message.")
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
public fun encrypt(data: ByteArray, groupPublicKey: String, userPublicKey: String): ByteArray {
|
|
||||||
// 1. ) Encrypt the data with the user's sender key
|
|
||||||
val ciphertextAndKeyIndex = SharedSenderKeysImplementation.shared.encrypt(data, groupPublicKey, userPublicKey)
|
|
||||||
val ivAndCiphertext = ciphertextAndKeyIndex.first
|
|
||||||
val keyIndex = ciphertextAndKeyIndex.second
|
|
||||||
val x0 = ClosedGroupCiphertextMessage(ivAndCiphertext, Hex.fromStringCondensed(userPublicKey), keyIndex);
|
|
||||||
// 2. ) Encrypt the result for the group's public key to hide the sender public key and key index
|
|
||||||
val x1 = EncryptionUtilities.encryptForX25519PublicKey(x0.serialize(), groupPublicKey.removing05PrefixIfNeeded())
|
|
||||||
// 3. ) Wrap the result
|
|
||||||
return SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.newBuilder()
|
|
||||||
.setCiphertext(ByteString.copyFrom(x1.ciphertext))
|
|
||||||
.setEphemeralPublicKey(ByteString.copyFrom(x1.ephemeralPublicKey))
|
|
||||||
.build().toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
public fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String> {
|
|
||||||
// 1. ) Check preconditions
|
|
||||||
val groupPublicKey = envelope.source
|
|
||||||
if (groupPublicKey == null || !SharedSenderKeysImplementation.shared.isClosedGroup(groupPublicKey)) {
|
|
||||||
throw Error.InvalidGroupPublicKey
|
|
||||||
}
|
|
||||||
val data = envelope.content
|
|
||||||
if (data.count() == 0) {
|
|
||||||
throw Error.NoData
|
|
||||||
}
|
|
||||||
val groupPrivateKey = SharedSenderKeysImplementation.shared.getKeyPair(groupPublicKey)?.privateKey?.serialize()
|
|
||||||
if (groupPrivateKey == null) {
|
|
||||||
throw Error.NoGroupPrivateKey
|
|
||||||
}
|
|
||||||
// 2. ) Parse the wrapper
|
|
||||||
val x0 = SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.parseFrom(data)
|
|
||||||
val ivAndCiphertext = x0.ciphertext.toByteArray()
|
|
||||||
val ephemeralPublicKey = x0.ephemeralPublicKey.toByteArray()
|
|
||||||
// 3. ) Decrypt the data inside
|
|
||||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey)
|
|
||||||
val mac = Mac.getInstance("HmacSHA256")
|
|
||||||
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
|
||||||
val symmetricKey = mac.doFinal(ephemeralSharedSecret)
|
|
||||||
val x1 = DecryptionUtilities.decryptUsingAESGCM(ivAndCiphertext, symmetricKey)
|
|
||||||
// 4. ) Parse the closed group ciphertext message
|
|
||||||
val x2 = ClosedGroupCiphertextMessage.from(x1)
|
|
||||||
if (x2 == null) {
|
|
||||||
throw Error.ParsingFailed
|
|
||||||
}
|
|
||||||
val senderPublicKey = x2.senderPublicKey.toHexString()
|
|
||||||
// 5. ) Use the info inside the closed group ciphertext message to decrypt the actual message content
|
|
||||||
val plaintext = SharedSenderKeysImplementation.shared.decrypt(x2.ivAndCiphertext, groupPublicKey, senderPublicKey, x2.keyIndex)
|
|
||||||
// 6. ) Return
|
|
||||||
return Pair(plaintext, senderPublicKey)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.closedgroups
|
|
||||||
|
|
||||||
enum class ClosedGroupRatchetCollectionType { Old, Current }
|
|
||||||
|
|
||||||
interface SharedSenderKeysDatabaseProtocol {
|
|
||||||
|
|
||||||
// region Ratchets & Sender Keys
|
|
||||||
fun getClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, collection: ClosedGroupRatchetCollectionType): ClosedGroupRatchet?
|
|
||||||
fun setClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, collection: ClosedGroupRatchetCollectionType)
|
|
||||||
fun removeAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType)
|
|
||||||
fun getAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set<Pair<String, ClosedGroupRatchet>>
|
|
||||||
fun getAllClosedGroupSenderKeys(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set<ClosedGroupSenderKey>
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Private & Public Keys
|
|
||||||
fun getClosedGroupPrivateKey(groupPublicKey: String): String?
|
|
||||||
fun setClosedGroupPrivateKey(groupPublicKey: String, groupPrivateKey: String)
|
|
||||||
fun removeClosedGroupPrivateKey(groupPublicKey: String)
|
|
||||||
fun getAllClosedGroupPublicKeys(): Set<String>
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Convenience
|
|
||||||
fun isSSKBasedClosedGroup(groupPublicKey: String): Boolean
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.closedgroups
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
|
||||||
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import org.session.libsignal.libsignal.util.ByteUtil
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.service.internal.util.Util
|
|
||||||
import org.session.libsignal.service.loki.api.utilities.EncryptionUtilities
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.Mac
|
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
public final class SharedSenderKeysImplementation(private val database: SharedSenderKeysDatabaseProtocol, private val delegate: SharedSenderKeysImplementationDelegate) {
|
|
||||||
private val gcmTagSize = 128
|
|
||||||
private val ivSize = 12
|
|
||||||
|
|
||||||
// A quick overview of how shared sender key based closed groups work:
|
|
||||||
//
|
|
||||||
// • When a user creates a group, they generate a key pair for the group along with a ratchet for
|
|
||||||
// every member of the group. They bundle this together with some other group info such as the group
|
|
||||||
// name in a `ClosedGroupUpdateMessage` and send that using established channels to every member of
|
|
||||||
// the group. Note that because a user can only pick from their existing contacts when selecting
|
|
||||||
// the group members they shouldn't need to establish sessions before being able to send the
|
|
||||||
// `ClosedGroupUpdateMessage`.
|
|
||||||
// • After the group is created, every user polls for the public key associated with the group.
|
|
||||||
// • Upon receiving a `ClosedGroupUpdateMessage` of type `.new`, a user sends session requests to all
|
|
||||||
// other members of the group they don't yet have a session with for reasons outlined below.
|
|
||||||
// • When a user sends a message they step their ratchet and use the resulting message key to encrypt
|
|
||||||
// the message.
|
|
||||||
// • When another user receives that message, they step the ratchet associated with the sender and
|
|
||||||
// use the resulting message key to decrypt the message.
|
|
||||||
// • When a user leaves or is kicked from a group, all members must generate new ratchets to ensure that
|
|
||||||
// removed users can't decrypt messages going forward. To this end every user deletes all ratchets
|
|
||||||
// associated with the group in question upon receiving a group update message that indicates that
|
|
||||||
// a user left. They then generate a new ratchet for themselves and send it out to all members of
|
|
||||||
// the group. The user should already have established sessions with all other members at this point
|
|
||||||
// because of the behavior outlined a few points above.
|
|
||||||
// • When a user adds a new member to the group, they generate a ratchet for that new member and
|
|
||||||
// send that bundled in a `ClosedGroupUpdateMessage` to the group. They send a
|
|
||||||
// `ClosedGroupUpdateMessage` with the newly generated ratchet but also the existing ratchets of
|
|
||||||
// every other member of the group to the user that joined.
|
|
||||||
|
|
||||||
// region Initialization
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
public lateinit var shared: SharedSenderKeysImplementation
|
|
||||||
|
|
||||||
public fun configureIfNeeded(database: SharedSenderKeysDatabaseProtocol, delegate: SharedSenderKeysImplementationDelegate) {
|
|
||||||
if (::shared.isInitialized) { return; }
|
|
||||||
shared = SharedSenderKeysImplementation(database, delegate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Error
|
|
||||||
public class LoadingFailed(val groupPublicKey: String, val senderPublicKey: String)
|
|
||||||
: Exception("Couldn't get ratchet for closed group with public key: $groupPublicKey, sender public key: $senderPublicKey.")
|
|
||||||
public class MessageKeyMissing(val targetKeyIndex: Int, val groupPublicKey: String, val senderPublicKey: String)
|
|
||||||
: Exception("Couldn't find message key for old key index: $targetKeyIndex, public key: $groupPublicKey, sender public key: $senderPublicKey.")
|
|
||||||
public class GenericRatchetingException : Exception("An error occurred.")
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Private API
|
|
||||||
private fun hmac(key: ByteArray, input: ByteArray): ByteArray {
|
|
||||||
val mac = Mac.getInstance("HmacSHA256")
|
|
||||||
mac.init(SecretKeySpec(key, "HmacSHA256"))
|
|
||||||
return mac.doFinal(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun step(ratchet: ClosedGroupRatchet): ClosedGroupRatchet {
|
|
||||||
val nextMessageKey = hmac(Hex.fromStringCondensed(ratchet.chainKey), ByteArray(1) { 1.toByte() })
|
|
||||||
val nextChainKey = hmac(Hex.fromStringCondensed(ratchet.chainKey), ByteArray(1) { 2.toByte() })
|
|
||||||
val nextKeyIndex = ratchet.keyIndex + 1
|
|
||||||
val messageKeys = ratchet.messageKeys + listOf( nextMessageKey.toHexString() )
|
|
||||||
return ClosedGroupRatchet(nextChainKey.toHexString(), nextKeyIndex, messageKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync. Don't call from the main thread.
|
|
||||||
*/
|
|
||||||
private fun stepRatchetOnce(groupPublicKey: String, senderPublicKey: String): ClosedGroupRatchet {
|
|
||||||
val ratchet = database.getClosedGroupRatchet(groupPublicKey, senderPublicKey, ClosedGroupRatchetCollectionType.Current)
|
|
||||||
if (ratchet == null) {
|
|
||||||
val exception = LoadingFailed(groupPublicKey, senderPublicKey)
|
|
||||||
Log.d("Loki", exception.message ?: "An error occurred.")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
val result = step(ratchet)
|
|
||||||
database.setClosedGroupRatchet(groupPublicKey, senderPublicKey, result, ClosedGroupRatchetCollectionType.Current)
|
|
||||||
return result
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.d("Loki", "Couldn't step ratchet due to error: $exception.")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stepRatchet(groupPublicKey: String, senderPublicKey: String, targetKeyIndex: Int, isRetry: Boolean = false): ClosedGroupRatchet {
|
|
||||||
val collection = if (isRetry) ClosedGroupRatchetCollectionType.Old else ClosedGroupRatchetCollectionType.Current
|
|
||||||
val ratchet = database.getClosedGroupRatchet(groupPublicKey, senderPublicKey, collection)
|
|
||||||
if (ratchet == null) {
|
|
||||||
val exception = LoadingFailed(groupPublicKey, senderPublicKey)
|
|
||||||
Log.d("Loki", exception.message ?: "An error occurred.")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
if (targetKeyIndex < ratchet.keyIndex) {
|
|
||||||
// There's no need to advance the ratchet if this is invoked for an old key index
|
|
||||||
if (ratchet.messageKeys.count() <= targetKeyIndex) {
|
|
||||||
val exception = MessageKeyMissing(targetKeyIndex, groupPublicKey, senderPublicKey)
|
|
||||||
Log.d("Loki", exception.message ?: "An error occurred.")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
return ratchet
|
|
||||||
} else {
|
|
||||||
var currentKeyIndex = ratchet.keyIndex
|
|
||||||
var result: ClosedGroupRatchet = ratchet // Explicitly typed because otherwise the compiler has trouble inferring that this can't be null
|
|
||||||
while (currentKeyIndex < targetKeyIndex) {
|
|
||||||
try {
|
|
||||||
result = step(result)
|
|
||||||
currentKeyIndex = result.keyIndex
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.d("Loki", "Couldn't step ratchet due to error: $exception.")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val collection = if (isRetry) ClosedGroupRatchetCollectionType.Old else ClosedGroupRatchetCollectionType.Current
|
|
||||||
database.setClosedGroupRatchet(groupPublicKey, senderPublicKey, result, collection)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Public API
|
|
||||||
public fun generateRatchet(groupPublicKey: String, senderPublicKey: String): ClosedGroupRatchet {
|
|
||||||
val rootChainKey = Util.getSecretBytes(32).toHexString()
|
|
||||||
val ratchet = ClosedGroupRatchet(rootChainKey, 0, listOf())
|
|
||||||
database.setClosedGroupRatchet(groupPublicKey, senderPublicKey, ratchet, ClosedGroupRatchetCollectionType.Current)
|
|
||||||
return ratchet
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun encrypt(plaintext: ByteArray, groupPublicKey: String, senderPublicKey: String): Pair<ByteArray, Int> {
|
|
||||||
val ratchet: ClosedGroupRatchet
|
|
||||||
try {
|
|
||||||
ratchet = stepRatchetOnce(groupPublicKey, senderPublicKey)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
if (exception is LoadingFailed) {
|
|
||||||
delegate.requestSenderKey(groupPublicKey, senderPublicKey)
|
|
||||||
}
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
val iv = Util.getSecretBytes(ivSize)
|
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
|
||||||
val messageKey = ratchet.messageKeys.last()
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(Hex.fromStringCondensed(messageKey), "AES"), GCMParameterSpec(gcmTagSize, iv))
|
|
||||||
return Pair(ByteUtil.combine(iv, cipher.doFinal(plaintext)), ratchet.keyIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun decrypt(ivAndCiphertext: ByteArray, groupPublicKey: String, senderPublicKey: String, keyIndex: Int, isRetry: Boolean = false): ByteArray {
|
|
||||||
val ratchet: ClosedGroupRatchet
|
|
||||||
try {
|
|
||||||
ratchet = stepRatchet(groupPublicKey, senderPublicKey, keyIndex, isRetry)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
if (!isRetry) {
|
|
||||||
return decrypt(ivAndCiphertext, groupPublicKey, senderPublicKey, keyIndex, true)
|
|
||||||
} else {
|
|
||||||
if (exception is LoadingFailed) {
|
|
||||||
delegate.requestSenderKey(groupPublicKey, senderPublicKey)
|
|
||||||
}
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val iv = ivAndCiphertext.sliceArray(0 until ivSize)
|
|
||||||
val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.count())
|
|
||||||
val messageKeys = ratchet.messageKeys
|
|
||||||
val lastNMessageKeys: List<String>
|
|
||||||
if (messageKeys.count() > 16) { // Pick an arbitrary number of message keys to try; this helps resolve issues caused by messages arriving out of order
|
|
||||||
lastNMessageKeys = messageKeys.subList(messageKeys.lastIndex - 16, messageKeys.lastIndex)
|
|
||||||
} else {
|
|
||||||
lastNMessageKeys = messageKeys
|
|
||||||
}
|
|
||||||
if (lastNMessageKeys.isEmpty()) {
|
|
||||||
throw MessageKeyMissing(keyIndex, groupPublicKey, senderPublicKey)
|
|
||||||
}
|
|
||||||
var exception: Exception? = null
|
|
||||||
for (messageKey in lastNMessageKeys.reversed()) { // Reversed because most likely the last one is the one we need
|
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(Hex.fromStringCondensed(messageKey), "AES"), GCMParameterSpec(EncryptionUtilities.gcmTagSize, iv))
|
|
||||||
try {
|
|
||||||
return cipher.doFinal(ciphertext)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
exception = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isRetry) {
|
|
||||||
return decrypt(ivAndCiphertext, groupPublicKey, senderPublicKey, keyIndex, true)
|
|
||||||
} else {
|
|
||||||
delegate.requestSenderKey(groupPublicKey, senderPublicKey)
|
|
||||||
throw exception ?: GenericRatchetingException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun isClosedGroup(publicKey: String): Boolean {
|
|
||||||
return database.getAllClosedGroupPublicKeys().contains(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun getKeyPair(groupPublicKey: String): ECKeyPair? {
|
|
||||||
val privateKey = database.getClosedGroupPrivateKey(groupPublicKey) ?: return null
|
|
||||||
return ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(groupPublicKey.removing05PrefixIfNeeded())),
|
|
||||||
DjbECPrivateKey(Hex.fromStringCondensed(privateKey)))
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.closedgroups
|
|
||||||
|
|
||||||
public interface SharedSenderKeysImplementationDelegate {
|
|
||||||
|
|
||||||
public fun requestSenderKey(groupPublicKey: String, senderPublicKey: String)
|
|
||||||
}
|
|
@ -3,23 +3,20 @@ package org.session.libsignal.service.loki.protocol.sessionmanagement
|
|||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.session.libsignal.libsignal.loki.SessionResetProtocol
|
import org.session.libsignal.libsignal.loki.SessionResetProtocol
|
||||||
import org.session.libsignal.libsignal.loki.SessionResetStatus
|
import org.session.libsignal.libsignal.loki.SessionResetStatus
|
||||||
import org.session.libsignal.libsignal.state.SignalProtocolStore
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional
|
import org.session.libsignal.libsignal.util.guava.Optional
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageSender
|
import org.session.libsignal.service.api.SignalServiceMessageSender
|
||||||
import org.session.libsignal.service.api.push.SignalServiceAddress
|
import org.session.libsignal.service.api.push.SignalServiceAddress
|
||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
|
||||||
|
|
||||||
public class SessionManagementProtocol(private val sessionResetImpl: SessionResetProtocol, private val sskDatabase: SharedSenderKeysDatabaseProtocol,
|
public class SessionManagementProtocol(private val sessionResetImpl: SessionResetProtocol, private val delegate: SessionManagementProtocolDelegate) {
|
||||||
private val delegate: SessionManagementProtocolDelegate) {
|
|
||||||
|
|
||||||
// region Initialization
|
// region Initialization
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
public lateinit var shared: SessionManagementProtocol
|
public lateinit var shared: SessionManagementProtocol
|
||||||
|
|
||||||
public fun configureIfNeeded(sessionResetImpl: SessionResetProtocol, sskDatabase: SharedSenderKeysDatabaseProtocol, delegate: SessionManagementProtocolDelegate) {
|
public fun configureIfNeeded(sessionResetImpl: SessionResetProtocol, delegate: SessionManagementProtocolDelegate) {
|
||||||
if (::shared.isInitialized) { return; }
|
if (::shared.isInitialized) { return; }
|
||||||
shared = SessionManagementProtocol(sessionResetImpl, sskDatabase, delegate)
|
shared = SessionManagementProtocol(sessionResetImpl, delegate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
Loading…
x
Reference in New Issue
Block a user