Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/SecureValueRecovery/MasterKeySyncManager.swift
1 views
//
// 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 hasDistributedAEP = "hasSyncedAEP"
        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: SyncManagerProtocolSwift
    private let tsAccountManager: TSAccountManager

    init(
        dateProvider: @escaping DateProvider,
        svr: SecureValueRecovery,
        syncManager: SyncManagerProtocolSwift,
        tsAccountManager: TSAccountManager,
    ) {
        self.dateProvider = dateProvider
        self.keyValueStore = KeyValueStore(collection: StoreConstants.collectionName)
        self.svr = svr
        self.syncManager = syncManager
        self.tsAccountManager = tsAccountManager
    }

    func runStartupJobs(tx: DBWriteTransaction) {
        guard let registeredState = try? tsAccountManager.registeredState(tx: tx) else {
            return
        }
        if registeredState.isPrimary {
            runStartupJobsForPrimaryDevice(tx: tx)
        } else {
            runStartupJobsForLinkedDevice(tx: tx)
        }
    }

    private func runStartupJobsForPrimaryDevice(tx: DBWriteTransaction) {
        let key = StoreConstants.hasDistributedAEP
        guard
            !keyValueStore.getBool(
                key,
                defaultValue: false,
                transaction: tx,
            )
        else {
            return
        }

        logger.info("Sending one-time keys sync message.")
        syncManager.sendKeysSyncMessage(tx: tx)

        self.keyValueStore.setBool(
            true,
            key: key,
            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(transaction: tx)

        keyValueStore.setDate(
            dateProvider(),
            key: StoreConstants.lastKeysSyncRequestMessageDateKey,
            transaction: tx,
        )
    }
}