// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // public protocol MasterKeySyncManager { /// Runs startup jobs required to sync the master key to linked devices. /// /// Historically linked devices did not have the master key; now that /// we have started including it in the Keys SyncMessage, we have to /// do some work to make sure that sync message happens proactively. /// /// On a primary device, this will trigger a one-time Keys SyncMessage if /// we haven't already sent one. /// /// On a linked device, this will send a sync keys request if we don't have /// a master key available locally. func runStartupJobs(tx: DBWriteTransaction) } class MasterKeySyncManagerImpl: MasterKeySyncManager { private enum StoreConstants { static let collectionName = "MasterKeyOneTimeSyncManager" static let hasDistributedMasterKey = "hasSyncedMasterKey" static let lastKeysSyncRequestMessageDateKey = "lastKeysSyncRequestMessageDateKey" } private let logger = PrefixedLogger(prefix: "MKSM") private let dateProvider: DateProvider private let keyValueStore: KeyValueStore private let svr: SecureValueRecovery private let syncManager: Shims.SyncManager private let tsAccountManager: TSAccountManager init( dateProvider: @escaping DateProvider, keyValueStoreFactory: KeyValueStoreFactory, svr: SecureValueRecovery, syncManager: Shims.SyncManager, tsAccountManager: TSAccountManager ) { self.dateProvider = dateProvider self.keyValueStore = keyValueStoreFactory.keyValueStore(collection: StoreConstants.collectionName) self.svr = svr self.syncManager = syncManager self.tsAccountManager = tsAccountManager } func runStartupJobs(tx: DBWriteTransaction) { switch tsAccountManager.registrationState(tx: tx) { case .registered: runStartupJobsForPrimaryDevice(tx: tx) case .provisioned: runStartupJobsForLinkedDevice(tx: tx) case .delinked, .deregistered, .unregistered, .transferred, .transferringIncoming, .transferringLinkedOutgoing, .transferringPrimaryOutgoing, .reregistering, .relinking: logger.info("Skipping; not registered") return } } private func runStartupJobsForPrimaryDevice(tx: DBWriteTransaction) { guard !keyValueStore.getBool( StoreConstants.hasDistributedMasterKey, defaultValue: false, transaction: tx ) else { return } logger.info("Sending one-time keys sync message.") syncManager.sendKeysSyncMessage(tx: tx) self.keyValueStore.setBool( true, key: StoreConstants.hasDistributedMasterKey, transaction: tx ) } private func runStartupJobsForLinkedDevice(tx: DBWriteTransaction) { if svr.hasMasterKey(transaction: tx) { // No need to sync; we have the master key. return } let lastRequestDate = keyValueStore.getDate( StoreConstants.lastKeysSyncRequestMessageDateKey, transaction: tx ) ?? .distantPast guard dateProvider().timeIntervalSince(lastRequestDate) >= 60 * 60 * 24 else { logger.info("Skipping keys sync request; too soon since last request.") return } logger.info("Requesting keys sync message") syncManager.sendKeysSyncRequestMessage(tx: tx) keyValueStore.setDate( dateProvider(), key: StoreConstants.lastKeysSyncRequestMessageDateKey, transaction: tx ) } } // MARK: - Dependencies extension MasterKeySyncManagerImpl { enum Shims { public typealias SyncManager = _MasterKeySyncManagerImpl_SyncManager_Shim } enum Wrappers { public typealias SyncManager = _MasterKeySyncManagerImpl_SyncManager_Wrapper } } // MARK: SyncManager protocol _MasterKeySyncManagerImpl_SyncManager_Shim { func sendKeysSyncMessage(tx: DBWriteTransaction) func sendKeysSyncRequestMessage(tx: DBWriteTransaction) } class _MasterKeySyncManagerImpl_SyncManager_Wrapper: _MasterKeySyncManagerImpl_SyncManager_Shim { private let syncManager: SyncManagerProtocolSwift init(_ syncManager: SyncManagerProtocolSwift) { self.syncManager = syncManager } func sendKeysSyncMessage(tx: DBWriteTransaction) { syncManager.sendKeysSyncMessage(tx: SDSDB.shimOnlyBridge(tx)) } func sendKeysSyncRequestMessage(tx: DBWriteTransaction) { syncManager.sendKeysSyncRequestMessage(transaction: SDSDB.shimOnlyBridge(tx)) } }