pull/218/head
nielsandriesse 4 years ago
parent 9de2cc210d
commit d2e0f986b9

@ -141,21 +141,27 @@ final class DeviceLinksVC : BaseVC, UITableViewDataSource, UITableViewDelegate,
private func removeDeviceLink(_ deviceLink: DeviceLink) {
FileServerAPI.removeDeviceLink(deviceLink).done { [weak self] in
let linkedDeviceHexEncodedPublicKey = deviceLink.other.hexEncodedPublicKey
guard let thread = TSContactThread.fetch(uniqueId: TSContactThread.threadId(fromContactId: linkedDeviceHexEncodedPublicKey)) else { return }
let linkedDevicePublicKey = deviceLink.other.hexEncodedPublicKey
guard let thread = TSContactThread.fetch(uniqueId: TSContactThread.threadId(fromContactId: linkedDevicePublicKey)) else { return }
let unlinkDeviceMessage = UnlinkDeviceMessage(thread: thread)
SSKEnvironment.shared.messageSender.send(unlinkDeviceMessage, success: {
let storage = OWSPrimaryStorage.shared()
try! Storage.writeSync { transaction in
storage.removePreKeyBundle(forContact: linkedDeviceHexEncodedPublicKey, transaction: transaction)
storage.deleteAllSessions(forContact: linkedDeviceHexEncodedPublicKey, protocolContext: transaction)
storage.removePreKeyBundle(forContact: linkedDevicePublicKey, transaction: transaction)
storage.deleteAllSessions(forContact: linkedDevicePublicKey, protocolContext: transaction)
for groupPublicKey in Storage.getUserClosedGroupPublicKeys() {
ClosedGroupsProtocol.removeMembers([ linkedDevicePublicKey ], from: groupPublicKey, using: transaction)
}
}
}, failure: { _ in
print("[Loki] Failed to send unlink device message.")
let storage = OWSPrimaryStorage.shared()
try! Storage.writeSync { transaction in
storage.removePreKeyBundle(forContact: linkedDeviceHexEncodedPublicKey, transaction: transaction)
storage.deleteAllSessions(forContact: linkedDeviceHexEncodedPublicKey, protocolContext: transaction)
storage.removePreKeyBundle(forContact: linkedDevicePublicKey, transaction: transaction)
storage.deleteAllSessions(forContact: linkedDevicePublicKey, protocolContext: transaction)
for groupPublicKey in Storage.getUserClosedGroupPublicKeys() {
ClosedGroupsProtocol.removeMembers([ linkedDevicePublicKey ], from: groupPublicKey, using: transaction)
}
}
})
self?.updateDeviceLinks()

@ -68,8 +68,8 @@ public final class ClosedGroupsProtocol : NSObject {
public static func addMembers(_ newMembers: Set<String>, to groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
// Prepare
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
let groupID = LKGroupUtilities.getEncodedClosedGroupID(groupPublicKey)
guard let thread = TSGroupThread.fetch(uniqueId: groupID, transaction: transaction) else {
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else {
return print("[Loki] Can't add users to nonexistent closed group.")
}
let group = thread.groupModel
@ -99,18 +99,18 @@ public final class ClosedGroupsProtocol : NSObject {
// Establish sessions if needed
establishSessionsIfNeeded(with: [String](newMembers), using: transaction) // Not `newMembersAndLinkedDevices` as this internally takes care of multi device already
// Send closed group update messages to the new members (and their linked devices) using established channels
let allSenderKeys = [ClosedGroupSenderKey](Storage.getAllClosedGroupSenderKeys(for: groupPublicKey)) // This includes the newly generated sender keys
var allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey)
allSenderKeys.formUnion(senderKeys)
for member in newMembers { // Not `newMembersAndLinkedDevices` as this internally takes care of multi device already
let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction)
thread.save(with: transaction)
let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name,
groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: allSenderKeys, members: members, admins: admins)
groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: members, admins: admins)
let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind)
messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction)
}
// Update the group
let groupIDAsData = groupID.data(using: String.Encoding.utf8)!
let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupIDAsData, groupType: .closedGroup, adminIds: admins)
let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins)
thread.setGroupModel(newGroupModel, with: transaction)
// Notify the user
let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate)
@ -130,8 +130,8 @@ public final class ClosedGroupsProtocol : NSObject {
return print("[Loki] Can't remove self and others simultaneously.")
}
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
let groupID = LKGroupUtilities.getEncodedClosedGroupID(groupPublicKey)
guard let thread = TSGroupThread.fetch(uniqueId: groupID, transaction: transaction) else {
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else {
return print("[Loki] Can't remove users from nonexistent closed group.")
}
let group = thread.groupModel
@ -171,8 +171,7 @@ public final class ClosedGroupsProtocol : NSObject {
}
}
// Update the group
let groupIDAsData = groupID.data(using: String.Encoding.utf8)!
let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupIDAsData, groupType: .closedGroup, adminIds: admins)
let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins)
thread.setGroupModel(newGroupModel, with: transaction)
// Notify the user
let infoMessageType: TSInfoMessageType = isUserLeaving ? .typeGroupQuit : .typeGroupUpdate
@ -234,19 +233,11 @@ public final class ClosedGroupsProtocol : NSObject {
let members = closedGroupUpdate.members
let admins = closedGroupUpdate.admins
// Get the group
let groupID = LKGroupUtilities.getEncodedClosedGroupID(groupPublicKey)
guard let thread = TSGroupThread.fetch(uniqueId: groupID, transaction: transaction) else {
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else {
return print("[Loki] Ignoring closed group update for nonexistent group.")
}
let group = thread.groupModel
// Check that the sender is an admin (before the update)
var isSenderAdmin = false
Storage.read { transaction in
isSenderAdmin = !thread.isUserAdmin(inGroup: senderPublicKey, transaction: transaction)
}
guard isSenderAdmin else {
return print("[Loki] Ignoring closed group update from non-admin.")
}
// Store the ratchets for any new members (it's important that this happens before the code below)
senderKeys.forEach { senderKey in
let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: [])
@ -276,8 +267,7 @@ public final class ClosedGroupsProtocol : NSObject {
}
}
// Update the group
let groupIDAsData = groupID.data(using: String.Encoding.utf8)!
let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupIDAsData, groupType: .closedGroup, adminIds: admins)
let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins)
thread.setGroupModel(newGroupModel, with: transaction)
// Notify the user
let infoMessageType: TSInfoMessageType = wasUserRemoved ? .typeGroupQuit : .typeGroupUpdate

@ -1,5 +1,5 @@
internal extension Storage {
public extension Storage {
// MARK: Ratchets
internal static func getClosedGroupRatchetCollection(for groupPublicKey: String) -> String {
@ -39,12 +39,12 @@ internal extension Storage {
}
}
@objc internal extension Storage {
@objc public extension Storage {
// MARK: Private Keys
internal static let closedGroupPrivateKeyCollection = "LokiClosedGroupPrivateKeyCollection"
internal static func getUserClosedGroupPublicKeys() -> Set<String> {
public static func getUserClosedGroupPublicKeys() -> Set<String> {
var result: Set<String> = []
read { transaction in
result = Set(transaction.allKeys(inCollection: closedGroupPrivateKeyCollection))

@ -100,18 +100,31 @@ public final class SessionMetaProtocol : NSObject {
// MARK: - Receiving
@objc(shouldSkipMessageDecryptResult:)
public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult) -> Bool {
// Called from OWSMessageReceiver to prevent messages from even being added to the processing queue.
// This intentionally doesn't take into account multi device.
return result.source == getUserHexEncodedPublicKey() // Should never occur
@objc(shouldSkipMessageDecryptResult:wrappedIn:)
public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult, wrappedIn envelope: SSKProtoEnvelope) -> Bool {
if result.source == getUserHexEncodedPublicKey() { return true }
var isLinkedDevice = false
Storage.read { transaction in
isLinkedDevice = LokiDatabaseUtilities.isUserLinkedDevice(result.source, transaction: transaction)
}
return isLinkedDevice && envelope.type == .closedGroupCiphertext
}
@objc(updateDisplayNameIfNeededForPublicKey:using:transaction:)
public static func updateDisplayNameIfNeeded(for publicKey: String, using dataMessage: SSKProtoDataMessage, in transaction: YapDatabaseReadWriteTransaction) {
guard let profile = dataMessage.profile, let rawDisplayName = profile.displayName, !rawDisplayName.isEmpty else { return }
let shortID = publicKey.substring(from: publicKey.index(publicKey.endIndex, offsetBy: -8))
let displayName = "\(rawDisplayName) (...\(shortID))"
let displayName: String
if UserDefaults.standard[.masterHexEncodedPublicKey] == publicKey {
displayName = rawDisplayName
} else {
let shortID = publicKey.substring(from: publicKey.index(publicKey.endIndex, offsetBy: -8))
let suffix = "(...\(shortID))"
if rawDisplayName.hasSuffix(suffix) {
displayName = rawDisplayName // FIXME: Workaround for a bug where the raw display name already has the short ID attached to it
} else {
displayName = "\(rawDisplayName) \(suffix)"
}
}
let profileManager = SSKEnvironment.shared.profileManager
profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction)
}

@ -35,7 +35,7 @@ public final class SyncMessagesProtocol : NSObject {
guard device != userPublicKey else { continue }
let thread = TSContactThread.getOrCreateThread(withContactId: device, transaction: transaction)
thread.save(with: transaction)
let syncMessage = OWSOutgoingSyncMessage.init(in: thread, messageBody: "", attachmentId: nil)
let syncMessage = OWSOutgoingSyncMessage(in: thread, messageBody: "", attachmentId: nil)
syncMessage.save(with: transaction)
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: syncMessage, transaction: transaction)
@ -83,7 +83,7 @@ public final class SyncMessagesProtocol : NSObject {
// Generate ratchets for the user's linked devices
let userPublicKey = getUserHexEncodedPublicKey()
let masterPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey
let deviceLinks = storage.getDeviceLinks(for: userPublicKey, in: transaction)
let deviceLinks = storage.getDeviceLinks(for: masterPublicKey, in: transaction)
let linkedDevices = deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }.filter { $0 != userPublicKey }
let senderKeys: [ClosedGroupSenderKey] = linkedDevices.map { publicKey in
let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction)
@ -99,11 +99,12 @@ public final class SyncMessagesProtocol : NSObject {
sendMessageToGroup()
// Send closed group update messages to the linked devices using established channels
func sendMessageToLinkedDevices() {
let allSenderKeys = [ClosedGroupSenderKey](Storage.getAllClosedGroupSenderKeys(for: groupPublicKey)) // This includes the newly generated sender keys
var allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey)
allSenderKeys.formUnion(senderKeys)
let thread = TSContactThread.getOrCreateThread(withContactId: masterPublicKey, transaction: transaction)
thread.save(with: transaction)
let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name,
groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: allSenderKeys, members: members, admins: admins)
groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: members, admins: admins)
let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind)
messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device
}

@ -399,7 +399,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
OWSAssertDebug(transaction);
// Loki: Don't process any messages from ourself
if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result]) {
if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result wrappedIn:envelope]) {
dispatch_async(self.serialQueue, ^{
completion(YES);
});

@ -1509,8 +1509,17 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
recipientUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES];
}
// Loki: If the message was aimed at an SSK based closed group, aim the sync transcript at
// the contact thread with the other device rather than also sending it to the group.
__block TSThread *thread = message.thread;
if ([thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)thread).usesSharedSenderKeys) {
[LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) {
thread = [TSContactThread getThreadWithContactId:otherDevice transaction:transaction];
}];
}
OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript
thread:message.thread
thread:thread
recipient:recipient
senderCertificate:senderCertificate
udAccess:recipientUDAccess

Loading…
Cancel
Save