Use random bytes for profile rotation token

This commit is contained in:
Max Radermacher 2025-11-13 14:52:04 -06:00 committed by GitHub
parent f287ee8cfb
commit faf4618355
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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