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

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

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

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

@ -100,18 +100,31 @@ public final class SessionMetaProtocol : NSObject {
// MARK: - Receiving // MARK: - Receiving
@objc(shouldSkipMessageDecryptResult:) @objc(shouldSkipMessageDecryptResult:wrappedIn:)
public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult) -> Bool { public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult, wrappedIn envelope: SSKProtoEnvelope) -> Bool {
// Called from OWSMessageReceiver to prevent messages from even being added to the processing queue. if result.source == getUserHexEncodedPublicKey() { return true }
// This intentionally doesn't take into account multi device. var isLinkedDevice = false
return result.source == getUserHexEncodedPublicKey() // Should never occur Storage.read { transaction in
isLinkedDevice = LokiDatabaseUtilities.isUserLinkedDevice(result.source, transaction: transaction)
}
return isLinkedDevice && envelope.type == .closedGroupCiphertext
} }
@objc(updateDisplayNameIfNeededForPublicKey:using:transaction:) @objc(updateDisplayNameIfNeededForPublicKey:using:transaction:)
public static func updateDisplayNameIfNeeded(for publicKey: String, using dataMessage: SSKProtoDataMessage, in transaction: YapDatabaseReadWriteTransaction) { 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 } 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: String
let displayName = "\(rawDisplayName) (...\(shortID))" 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 let profileManager = SSKEnvironment.shared.profileManager
profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction) profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction)
} }

@ -35,7 +35,7 @@ public final class SyncMessagesProtocol : NSObject {
guard device != userPublicKey else { continue } guard device != userPublicKey else { continue }
let thread = TSContactThread.getOrCreateThread(withContactId: device, transaction: transaction) let thread = TSContactThread.getOrCreateThread(withContactId: device, transaction: transaction)
thread.save(with: 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) syncMessage.save(with: transaction)
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: syncMessage, transaction: transaction) messageSenderJobQueue.add(message: syncMessage, transaction: transaction)
@ -83,7 +83,7 @@ public final class SyncMessagesProtocol : NSObject {
// Generate ratchets for the user's linked devices // Generate ratchets for the user's linked devices
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
let masterPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey 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 linkedDevices = deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }.filter { $0 != userPublicKey }
let senderKeys: [ClosedGroupSenderKey] = linkedDevices.map { publicKey in let senderKeys: [ClosedGroupSenderKey] = linkedDevices.map { publicKey in
let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction)
@ -99,11 +99,12 @@ public final class SyncMessagesProtocol : NSObject {
sendMessageToGroup() sendMessageToGroup()
// Send closed group update messages to the linked devices using established channels // Send closed group update messages to the linked devices using established channels
func sendMessageToLinkedDevices() { 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) let thread = TSContactThread.getOrCreateThread(withContactId: masterPublicKey, transaction: transaction)
thread.save(with: transaction) thread.save(with: transaction)
let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, 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) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind)
messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device
} }

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

@ -1509,8 +1509,17 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
recipientUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES]; 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 OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript
thread:message.thread thread:thread
recipient:recipient recipient:recipient
senderCertificate:senderCertificate senderCertificate:senderCertificate
udAccess:recipientUDAccess udAccess:recipientUDAccess

Loading…
Cancel
Save