diff --git a/SignalServiceKit/Profiles/OWSProfileManager.swift b/SignalServiceKit/Profiles/OWSProfileManager.swift index 67371c3d94..c36c324dc3 100644 --- a/SignalServiceKit/Profiles/OWSProfileManager.swift +++ b/SignalServiceKit/Profiles/OWSProfileManager.swift @@ -390,7 +390,7 @@ public class OWSProfileManager: ProfileManagerProtocol { // MARK: - Profile Key Rotation public func forceRotateLocalProfileKeyForGroupDeparture(with transaction: DBWriteTransaction) { - forceRotateLocalProfileKeyForGroupDepartureObjc(tx: transaction) + _forceRotateLocalProfileKeyForGroupDeparture(tx: transaction) } public func groupKey(groupId: Data) -> String { @@ -613,8 +613,7 @@ extension OWSProfileManager: ProfileManager { let lastGroupProfileKeyCheckTimestamp = self.lastGroupProfileKeyCheckTimestamp(tx: tx) let triggers = [ self.blocklistRotationTriggerIfNeeded(tx: tx), - self.recipientHidingTriggerIfNeeded(tx: tx), - self.leaveGroupTriggerIfNeeded(tx: tx) + self.tokenTriggerIfNeeded(tx: tx), ].compacted() guard !triggers.isEmpty else { @@ -653,18 +652,9 @@ extension OWSProfileManager: ProfileManager { let groupIds: [Data] } - /// When we hide a recipient, we immediately update the whitelist and asynchronously - /// do a rotation. The date is when we set this trigger; if we _started_ a rotation - /// after this date, the condition is satisfied when the rotation completes. Otherwise - /// a rotation is needed. - case recipientHiding(Date) - - /// When we leave a group, that group had a hidden/blocked recipient, and we have no - /// other groups in common with that recipient, we rotate (so they lose access to our latest - /// profile key). - /// The date is when we set this trigger; if we _started_ a rotation after this date, the - /// condition is satisfied when the rotation completes. Otherwise a rotation is needed. - case leftGroupWithHiddenOrBlockedRecipient(Date) + /// We save a token when scheduling a profile key rotation. We schedule + /// *another* rotation if the token changes before we finish. + case tokenData(Data) } private func blocklistRotationTriggerIfNeeded(tx: DBReadTransaction) -> RotateProfileKeyTrigger? { @@ -683,24 +673,13 @@ extension OWSProfileManager: ProfileManager { )) } - private func recipientHidingTriggerIfNeeded(tx: DBReadTransaction) -> RotateProfileKeyTrigger? { - // If it's not nil, we should rotate. After rotating, we always write nil (if it succeeded), - // so presence is the only trigger. - // The actual date value is only used to disambiguate if a _new_ trigger got added while rotating. - guard let triggerDate = self.recipientHidingTriggerTimestamp(tx: tx) else { + private func tokenTriggerIfNeeded(tx: DBReadTransaction) -> RotateProfileKeyTrigger? { + // If it's not nil, we should rotate. After rotating, if it hasn't changed, + // we write nil, so presence is the only trigger. + guard let triggerToken = self.triggerToken(tx: tx) else { return nil } - return .recipientHiding(triggerDate) - } - - private func leaveGroupTriggerIfNeeded(tx: DBReadTransaction) -> RotateProfileKeyTrigger? { - // If it's not nil, we should rotate. After rotating, we always write nil (if it succeeded), - // so presence is the only trigger. - // The actual date value is only used to disambiguate if a _new_ trigger got added while rotating. - guard let triggerDate = self.leaveGroupTriggerTimestamp(tx: tx) else { - return nil - } - return .leftGroupWithHiddenOrBlockedRecipient(triggerDate) + return .tokenData(triggerToken) } @MainActor @@ -732,8 +711,6 @@ extension OWSProfileManager: ProfileManager { throw OWSAssertionError("tsAccountManager.isRegistered was unexpectedly false") } - let rotationStartDate = Date() - Logger.info("Beginning profile key rotation.") // The order of operations here is very important to prevent races between @@ -789,18 +766,8 @@ extension OWSProfileManager: ProfileManager { switch trigger { case .blocklistChange(let values): self.didRotateProfileKeyFromBlocklistTrigger(values, tx: tx) - case .recipientHiding(let triggerDate): - needsAnotherRotation = needsAnotherRotation || self.didRotateProfileKeyFromHidingTrigger( - rotationStartDate: rotationStartDate, - triggerDate: triggerDate, - tx: tx - ) - case .leftGroupWithHiddenOrBlockedRecipient(let triggerDate): - needsAnotherRotation = needsAnotherRotation || self.didRotateProfileKeyFromLeaveGroupTrigger( - rotationStartDate: rotationStartDate, - triggerDate: triggerDate, - tx: tx - ) + case .tokenData(let tokenData): + needsAnotherRotation = !self.clearTriggerToken(tokenData, tx: tx) || needsAnotherRotation } } @@ -837,45 +804,14 @@ extension OWSProfileManager: ProfileManager { ) } - // Returns true if another rotation is needed. - private func didRotateProfileKeyFromHidingTrigger( - rotationStartDate: Date, - triggerDate: Date, - tx: DBWriteTransaction - ) -> Bool { + // Returns true if the trigger was cleared. + private func clearTriggerToken(_ tokenData: Data, tx: DBWriteTransaction) -> Bool { // Fetch the latest trigger date, it might have changed if we triggered // a rotation again. - guard let latestTriggerDate = self.recipientHidingTriggerTimestamp(tx: tx) else { - // If it's been wiped, we are good to go. + guard tokenData == self.triggerToken(tx: tx) else { return false } - if rotationStartDate > latestTriggerDate { - // We can wipe; we started rotating after the trigger came in. - self.setRecipientHidingTriggerTimestamp(nil, tx: tx) - return false - } - // We need another rotation. - return true - } - - // Returns true if another rotation is needed. - private func didRotateProfileKeyFromLeaveGroupTrigger( - rotationStartDate: Date, - triggerDate: Date, - tx: DBWriteTransaction - ) -> Bool { - // Fetch the latest trigger date, it might have changed if we triggered - // a rotation again. - guard let latestTriggerDate = self.leaveGroupTriggerTimestamp(tx: tx) else { - // If it's been wiped, we are good to go. - return false - } - if rotationStartDate > latestTriggerDate { - // We can wipe; we started rotating after the trigger came in. - self.setLeaveGroupTriggerTimestamp(nil, tx: tx) - return false - } - // We need another rotation. + self.setTriggerToken(nil, tx: tx) return true } @@ -1562,11 +1498,11 @@ extension OWSProfileManager: ProfileManager { } // We schedule in the NSE by writing state; the actual rotation // will bail early, though. - self.setRecipientHidingTriggerTimestamp(Date(), tx: tx) + self.setTriggerToken(Randomness.generateRandomBytes(16), tx: tx) self.rotateProfileKeyIfNecessary(tx: tx) } - func forceRotateLocalProfileKeyForGroupDepartureObjc(tx: DBWriteTransaction) { + fileprivate func _forceRotateLocalProfileKeyForGroupDeparture(tx: DBWriteTransaction) { let tsRegistrationState = DependenciesBridge.shared.tsAccountManager.registrationState(tx: tx) guard tsRegistrationState.isRegistered else { return @@ -1576,7 +1512,7 @@ extension OWSProfileManager: ProfileManager { } // We schedule in the NSE by writing state; the actual rotation // will bail early, though. - self.setLeaveGroupTriggerTimestamp(Date(), tx: tx) + self.setTriggerToken(Randomness.generateRandomBytes(16), tx: tx) self.rotateProfileKeyIfNecessary(tx: tx) } @@ -1592,32 +1528,23 @@ extension OWSProfileManager: ProfileManager { return self.metadataStore.setDate(Date(), key: Self.kLastGroupProfileKeyCheckTimestampKey, transaction: tx) } - private static let recipientHidingTriggerTimestampKey = "recipientHidingTriggerTimestampKey" + private static let leaveGroupTriggerTokenKey = "leaveGroupTriggerTimestampKey" + private static let deprecated_recipientHidingTriggerTokenKey = "recipientHidingTriggerTimestampKey" - private func recipientHidingTriggerTimestamp(tx: DBReadTransaction) -> Date? { - return self.metadataStore.getDate(Self.recipientHidingTriggerTimestampKey, transaction: tx) + private func triggerToken(tx: DBReadTransaction) -> Data? { + return ( + self.metadataStore.getData(Self.leaveGroupTriggerTokenKey, transaction: tx) + ?? self.metadataStore.getData(Self.deprecated_recipientHidingTriggerTokenKey, transaction: tx) + ) } - private func setRecipientHidingTriggerTimestamp(_ date: Date?, tx: DBWriteTransaction) { - guard let date else { - self.metadataStore.removeValue(forKey: Self.recipientHidingTriggerTimestampKey, transaction: tx) - return + private func setTriggerToken(_ tokenData: Data?, tx: DBWriteTransaction) { + if let tokenData { + self.metadataStore.setData(tokenData, key: Self.leaveGroupTriggerTokenKey, transaction: tx) + } else { + self.metadataStore.removeValue(forKey: Self.leaveGroupTriggerTokenKey, transaction: tx) } - return self.metadataStore.setDate(date, key: Self.recipientHidingTriggerTimestampKey, transaction: tx) - } - - private static let leaveGroupTriggerTimestampKey = "leaveGroupTriggerTimestampKey" - - private func leaveGroupTriggerTimestamp(tx: DBReadTransaction) -> Date? { - return self.metadataStore.getDate(Self.leaveGroupTriggerTimestampKey, transaction: tx) - } - - private func setLeaveGroupTriggerTimestamp(_ date: Date?, tx: DBWriteTransaction) { - guard let date else { - self.metadataStore.removeValue(forKey: Self.leaveGroupTriggerTimestampKey, transaction: tx) - return - } - return self.metadataStore.setDate(date, key: Self.leaveGroupTriggerTimestampKey, transaction: tx) + self.metadataStore.removeValue(forKey: Self.deprecated_recipientHidingTriggerTokenKey, transaction: tx) } // MARK: - Last Messaging Date