Add KeyTransparencyStore, instead of static methods on KeyTransparencyManager

This commit is contained in:
Sasha Weiss 2026-02-04 14:17:13 -08:00 committed by GitHub
parent 0703b073c3
commit e4e9d7013f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 197 additions and 128 deletions

View File

@ -164,14 +164,16 @@ class AdvancedPrivacySettingsViewController: OWSTableViewController2 {
),
isOn: {
let db = DependenciesBridge.shared.db
let keyTransparencyStore = KeyTransparencyStore()
return db.read { tx in
KeyTransparencyManager.isEnabled(tx: tx)
keyTransparencyStore.isEnabled(tx: tx)
}
},
actionBlock: { uiSwitch in
let db = DependenciesBridge.shared.db
let keyTransparencyStore = KeyTransparencyStore()
db.write { tx in
KeyTransparencyManager.setIsEnabled(uiSwitch.isOn, tx: tx)
keyTransparencyStore.setIsEnabled(uiSwitch.isOn, tx: tx)
}
},
))

View File

@ -14,6 +14,7 @@ class DebugUIPrompts: DebugUIPage {
func section(thread: TSThread?) -> OWSTableSection? {
let db = DependenciesBridge.shared.db
let inactiveLinkedDeviceFinder = DependenciesBridge.shared.inactiveLinkedDeviceFinder
let keyTransparencyStore = KeyTransparencyStore()
let usernameEducationManager = DependenciesBridge.shared.usernameEducationManager
var items = [OWSTableItem]()
@ -21,7 +22,7 @@ class DebugUIPrompts: DebugUIPage {
items += [
OWSTableItem(title: "Reenable KT first-time education", actionBlock: {
db.write { tx in
KeyTransparencyManager.setHasShownFirstTimeEducation(false, tx: tx)
keyTransparencyStore.setHasShownFirstTimeEducation(false, tx: tx)
}
}),

View File

@ -53,6 +53,7 @@ class ChatListFYISheetCoordinator {
private let donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore
private let donationSubscriptionManager: DonationSubscriptionManager.Type
private let db: DB
private let keyTransparencyStore: KeyTransparencyStore
private let networkManager: NetworkManager
private let profileManager: ProfileManager
@ -62,6 +63,7 @@ class ChatListFYISheetCoordinator {
donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore,
donationSubscriptionManager: DonationSubscriptionManager.Type,
db: DB,
keyTransparencyStore: KeyTransparencyStore,
networkManager: NetworkManager,
profileManager: ProfileManager,
) {
@ -70,6 +72,7 @@ class ChatListFYISheetCoordinator {
self.donationReceiptCredentialResultStore = donationReceiptCredentialResultStore
self.donationSubscriptionManager = donationSubscriptionManager
self.db = db
self.keyTransparencyStore = keyTransparencyStore
self.networkManager = networkManager
self.profileManager = profileManager
}
@ -116,7 +119,7 @@ class ChatListFYISheetCoordinator {
return .backupSubscriptionExpired(FYISheet.BackupSubscriptionExpired(subscriptionType: .testFlight))
} else if backupSubscriptionIssueStore.shouldWarnIAPSubscriptionFailedToRenew(tx: tx) {
return .backupSubscriptionFailedToRenew(FYISheet.BackupSubscriptionFailedToRenew())
} else if KeyTransparencyManager.shouldWarnSelfCheckFailed(tx: tx) {
} else if keyTransparencyStore.shouldWarnSelfCheckFailed(tx: tx) {
return .keyTransparencySelfCheckFailed(FYISheet.KeyTransparencySelfCheckFailed())
} else {
return nil
@ -453,7 +456,7 @@ class ChatListFYISheetCoordinator {
chatListViewController.present(sheet, animated: true) { [self] in
db.write { tx in
KeyTransparencyManager.setWarnedSelfCheckFailed(tx: tx)
keyTransparencyStore.setWarnedSelfCheckFailed(tx: tx)
}
}
}

View File

@ -372,6 +372,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
donationReceiptCredentialResultStore: DependenciesBridge.shared.donationReceiptCredentialResultStore,
donationSubscriptionManager: DonationSubscriptionManager.self,
db: DependenciesBridge.shared.db,
keyTransparencyStore: KeyTransparencyStore(),
networkManager: SSKEnvironment.shared.networkManagerRef,
profileManager: SSKEnvironment.shared.profileManagerRef,
)

View File

@ -58,6 +58,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
private let disappearingMessageConfigurationStore: DisappearingMessagesConfigurationStore
private let donationSubscriptionManager: BackupArchive.Shims.DonationSubscriptionManager
private let imageQuality: BackupArchive.Shims.ImageQuality
private let keyTransparencyStore: KeyTransparencyStore
private let linkPreviewSettingStore: LinkPreviewSettingStore
private let localUsernameManager: LocalUsernameManager
private let logger: PrefixedLogger
@ -86,6 +87,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
disappearingMessageConfigurationStore: DisappearingMessagesConfigurationStore,
donationSubscriptionManager: BackupArchive.Shims.DonationSubscriptionManager,
imageQuality: BackupArchive.Shims.ImageQuality,
keyTransparencyStore: KeyTransparencyStore,
linkPreviewSettingStore: LinkPreviewSettingStore,
localUsernameManager: LocalUsernameManager,
mediaBandwidthPreferenceStore: MediaBandwidthPreferenceStore,
@ -112,6 +114,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
self.disappearingMessageConfigurationStore = disappearingMessageConfigurationStore
self.donationSubscriptionManager = donationSubscriptionManager
self.imageQuality = imageQuality
self.keyTransparencyStore = keyTransparencyStore
self.linkPreviewSettingStore = linkPreviewSettingStore
self.localUsernameManager = localUsernameManager
self.logger = PrefixedLogger(prefix: "[Backups]")
@ -195,7 +198,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
}
if
let keyTransparencyBlob = KeyTransparencyManager.getKeyTransparencyBlob(
let keyTransparencyBlob = keyTransparencyStore.getKeyTransparencyBlob(
aci: context.localIdentifiers.aci,
tx: context.tx,
)
@ -325,7 +328,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
}
accountSettings.allowSealedSenderFromAnyone = udManager.shouldAllowUnrestrictedAccessLocal(tx: context.tx)
accountSettings.allowAutomaticKeyVerification = KeyTransparencyManager.isEnabled(tx: context.tx)
accountSettings.allowAutomaticKeyVerification = keyTransparencyStore.isEnabled(tx: context.tx)
accountSettings.defaultSentMediaQuality = imageQuality.fetchValue(tx: context.tx) == .high ? .high : .standard
var downloadSettings = BackupProto_AccountData.AutoDownloadSettings()
@ -572,7 +575,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
}
udManager.setShouldAllowUnrestrictedAccessLocal(settings.allowSealedSenderFromAnyone, tx: context.tx)
KeyTransparencyManager.setIsEnabled(settings.allowAutomaticKeyVerification, tx: context.tx)
keyTransparencyStore.setIsEnabled(settings.allowAutomaticKeyVerification, tx: context.tx)
switch settings.defaultSentMediaQuality {
case .high:
@ -666,7 +669,7 @@ public class BackupArchiveAccountDataArchiver: BackupArchiveProtoStreamWriter {
}
if accountData.hasKeyTransparencyData {
KeyTransparencyManager.setKeyTransparencyBlob(
keyTransparencyStore.setKeyTransparencyBlob(
accountData.keyTransparencyData,
aci: context.localIdentifiers.aci,
tx: context.tx,

View File

@ -22,6 +22,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
private let avatarFetcher: BackupArchiveAvatarFetcher
private let blockingManager: BackupArchive.Shims.BlockingManager
private let contactManager: BackupArchive.Shims.ContactManager
private let keyTransparencyStore: KeyTransparencyStore
private let nicknameManager: NicknameManager
private let profileManager: BackupArchive.Shims.ProfileManager
private let recipientHidingManager: RecipientHidingManager
@ -38,6 +39,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
avatarFetcher: BackupArchiveAvatarFetcher,
blockingManager: BackupArchive.Shims.BlockingManager,
contactManager: BackupArchive.Shims.ContactManager,
keyTransparencyStore: KeyTransparencyStore,
nicknameManager: NicknameManager,
profileManager: BackupArchive.Shims.ProfileManager,
recipientHidingManager: RecipientHidingManager,
@ -53,6 +55,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
self.avatarFetcher = avatarFetcher
self.blockingManager = blockingManager
self.contactManager = contactManager
self.keyTransparencyStore = keyTransparencyStore
self.nicknameManager = nicknameManager
self.profileManager = profileManager
self.recipientHidingManager = recipientHidingManager
@ -264,7 +267,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
tx: context.tx,
),
keyTransparencyBlob: recipient.aci.flatMap { aci in
KeyTransparencyManager.getKeyTransparencyBlob(aci: aci, tx: context.tx)
self.keyTransparencyStore.getKeyTransparencyBlob(aci: aci, tx: context.tx)
},
)
@ -875,7 +878,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
contactProto.hasKeyTransparencyData,
let aci = backupContactAddress.aci
{
KeyTransparencyManager.setKeyTransparencyBlob(
keyTransparencyStore.setKeyTransparencyBlob(
contactProto.keyTransparencyData,
aci: aci,
tx: context.tx,

View File

@ -1233,11 +1233,13 @@ extension AppSetup.GlobalsContinuation {
whoAmIManager: whoAmIManager,
))
let keyTransparencyStore = KeyTransparencyStore()
let keyTransparencyManager = KeyTransparencyManager(
chatConnectionManager: chatConnectionManager,
dateProvider: dateProvider,
db: db,
identityManager: identityManager,
keyTransparencyStore: keyTransparencyStore,
localUsernameManager: localUsernameManager,
recipientDatabaseTable: recipientDatabaseTable,
storageServiceManager: storageServiceManager,
@ -1379,6 +1381,7 @@ extension AppSetup.GlobalsContinuation {
avatarFetcher: backupArchiveAvatarFetcher,
blockingManager: BackupArchive.Wrappers.BlockingManager(blockingManager),
contactManager: BackupArchive.Wrappers.ContactManager(contactManager),
keyTransparencyStore: keyTransparencyStore,
nicknameManager: nicknameManager,
profileManager: BackupArchive.Wrappers.ProfileManager(profileManager),
recipientHidingManager: recipientHidingManager,
@ -1423,6 +1426,7 @@ extension AppSetup.GlobalsContinuation {
disappearingMessageConfigurationStore: disappearingMessagesConfigurationStore,
donationSubscriptionManager: BackupArchive.Wrappers.DonationSubscriptionManager(),
imageQuality: BackupArchive.Wrappers.ImageQuality(),
keyTransparencyStore: keyTransparencyStore,
linkPreviewSettingStore: linkPreviewSettingStore,
localUsernameManager: localUsernameManager,
mediaBandwidthPreferenceStore: mediaBandwidthPreferenceStore,

View File

@ -10,29 +10,11 @@ public final class KeyTransparencyManager {
private static let logger = PrefixedLogger(prefix: "[KT]")
private var logger: PrefixedLogger { Self.logger }
private static let kvStore = NewKeyValueStore(collection: "KeyTransparencyManager")
private var kvStore: NewKeyValueStore { Self.kvStore }
/// Keys for `kvStore`.
/// - Important
/// If you're adding a new key here, consider whether it should be wiped
/// when Key Transparency is disabled. See: `setIsEnabled`.
private enum KVStoreKeys {
/// Keys to a `Bool` representing whether or not KT is enabled.
static let isEnabled = "isEnabled"
/// Keys to a `SelfCheckState`'s raw value.
static let selfCheckState = "selfCheckState"
/// Keys to a `Bool` representing whether or not we should show
/// first-time education about KT.
static let shouldShowFirstTimeEducation = "shouldShowFirstTimeEducation"
/// Keys to an opaque LibSignalClient blob.
static let distinguishedTreeHead = "distinguishedTreeHead"
}
private let chatConnectionManager: ChatConnectionManager
private let dateProvider: DateProvider
private let db: DB
private let identityManager: OWSIdentityManager
private let keyTransparencyStore: KeyTransparencyStore
private let localUsernameManager: LocalUsernameManager
private let recipientDatabaseTable: RecipientDatabaseTable
private let storageServiceManager: StorageServiceManager
@ -46,6 +28,7 @@ public final class KeyTransparencyManager {
dateProvider: @escaping DateProvider,
db: DB,
identityManager: OWSIdentityManager,
keyTransparencyStore: KeyTransparencyStore,
localUsernameManager: LocalUsernameManager,
recipientDatabaseTable: RecipientDatabaseTable,
storageServiceManager: StorageServiceManager,
@ -56,6 +39,7 @@ public final class KeyTransparencyManager {
self.dateProvider = dateProvider
self.db = db
self.identityManager = identityManager
self.keyTransparencyStore = keyTransparencyStore
self.localUsernameManager = localUsernameManager
self.recipientDatabaseTable = recipientDatabaseTable
self.storageServiceManager = storageServiceManager
@ -98,7 +82,7 @@ public final class KeyTransparencyManager {
let logger = logger.suffixed(with: "[\(aci)]")
logger.info("")
guard Self.isEnabled(tx: tx) else {
guard keyTransparencyStore.isEnabled(tx: tx) else {
logger.warn("Is opted out.")
return nil
}
@ -193,20 +177,23 @@ public final class KeyTransparencyManager {
logger: PrefixedLogger,
) async throws {
let ktClient = try await chatConnectionManager.keyTransparencyClient()
let libSignalStore = KeyTransparencyStoreForLibSignal(db: db)
let libSignalStore = KeyTransparencyStoreForLibSignal(
db: db,
keyTransparencyStore: keyTransparencyStore,
)
let existingKeyTransparencyBlob: Data?
let selfCheckState: SelfCheckState?
let selfCheckState: KeyTransparencyStore.SelfCheckState?
(
existingKeyTransparencyBlob,
selfCheckState,
) = db.read { tx in
return (
Self.getKeyTransparencyBlob(
keyTransparencyStore.getKeyTransparencyBlob(
aci: params.aciInfo.aci,
tx: tx,
),
Self.selfCheckState(tx: tx),
keyTransparencyStore.selfCheckState(tx: tx),
)
}
@ -263,55 +250,8 @@ public final class KeyTransparencyManager {
}
}
// MARK: - Self-check state
private enum SelfCheckState: Int64 {
case succeeded = 1
case failedOnce = 2
case failedRepeatedly = 3
case failedRepeatedlyAndWarned = 4
}
private static func selfCheckState(tx: DBReadTransaction) -> SelfCheckState? {
return kvStore.fetchValue(
Int64.self,
forKey: KVStoreKeys.selfCheckState,
tx: tx,
)
.map { SelfCheckState(rawValue: $0)! }
}
private static func setSelfCheckState(_ state: SelfCheckState, tx: DBWriteTransaction) {
kvStore.writeValue(state.rawValue, forKey: KVStoreKeys.selfCheckState, tx: tx)
}
public static func shouldWarnSelfCheckFailed(tx: DBReadTransaction) -> Bool {
switch selfCheckState(tx: tx) {
case .failedRepeatedly:
return true
case nil, .succeeded, .failedOnce, .failedRepeatedlyAndWarned:
return false
}
}
public static func setWarnedSelfCheckFailed(tx: DBWriteTransaction) {
switch selfCheckState(tx: tx) {
case .failedRepeatedly:
Self.setSelfCheckState(.failedRepeatedlyAndWarned, tx: tx)
case nil, .succeeded, .failedOnce, .failedRepeatedlyAndWarned:
owsFailDebug("Unexpectedly setting warned, but shouldn't have warned?")
}
}
// MARK: - Self-check
private static let selfCheckCronStore = CronStore(uniqueKey: .keyTransparencySelfCheck)
private static let selfCheckCronInterval: TimeInterval = if BuildFlags.KeyTransparency.conservativeSelfCheck {
.day
} else {
.week
}
/// Use `Cron` to periodically perform a Key Transparency validation on the
/// local user.
public func registerSelfCheckForCron(cron: Cron) {
@ -323,25 +263,25 @@ public final class KeyTransparencyManager {
return false
},
operation: { [self] () async throws -> Void in
let mostRecentDate: Date
let localIdentifiers: LocalIdentifiers?
let isEnabled: Bool
let localIdentifiers: LocalIdentifiers?
let isTimeForSelfCheck: Bool
(
mostRecentDate,
localIdentifiers,
isEnabled,
localIdentifiers,
isTimeForSelfCheck,
) = db.read { tx in
return (
Self.selfCheckCronStore.mostRecentDate(tx: tx),
keyTransparencyStore.isEnabled(tx: tx),
tsAccountManager.localIdentifiers(tx: tx),
Self.isEnabled(tx: tx),
keyTransparencyStore.getIsTimeForSelfCheckCronJob(now: dateProvider(), tx: tx),
)
}
guard
isEnabled,
let localIdentifiers,
dateProvider() > mostRecentDate.addingTimeInterval(Self.selfCheckCronInterval)
isTimeForSelfCheck
else {
return
}
@ -366,7 +306,7 @@ public final class KeyTransparencyManager {
public func debugUI_setSelfCheckFailed() {
db.write { tx in
Self.setSelfCheckState(.failedRepeatedly, tx: tx)
keyTransparencyStore.setSelfCheckState(.failedRepeatedly, tx: tx)
}
}
@ -442,11 +382,10 @@ public final class KeyTransparencyManager {
await db.awaitableWrite { tx in
logger.info("Self-check success.")
Self.setSelfCheckState(.succeeded, tx: tx)
Self.selfCheckCronStore.setMostRecentDate(
dateProvider(),
jitter: Self.selfCheckCronInterval / Cron.jitterFactor,
keyTransparencyStore.setSelfCheckState(.succeeded, tx: tx)
keyTransparencyStore.setSelfCheckCronJobCompletedAt(
now: dateProvider(),
specialIntervalTillNextCron: nil,
tx: tx,
)
}
@ -461,14 +400,14 @@ public final class KeyTransparencyManager {
}
private func recordSelfCheckFailure(tx: DBWriteTransaction) {
let intervalTillNextCron: TimeInterval
let newSelfCheckState: SelfCheckState?
let specialIntervalTillNextCron: TimeInterval?
let newSelfCheckState: KeyTransparencyStore.SelfCheckState?
switch Self.selfCheckState(tx: tx) {
switch keyTransparencyStore.selfCheckState(tx: tx) {
case nil, .succeeded:
logger.warn("Self-check first failure.")
newSelfCheckState = .failedOnce
intervalTillNextCron = .day
specialIntervalTillNextCron = .day
// A known failure mode is if a linked device changed something
// KT-related (e.g., a username) and this device hasn't yet learned
@ -484,12 +423,12 @@ public final class KeyTransparencyManager {
case .failedOnce:
logger.warn("Self-check second failure.")
newSelfCheckState = .failedRepeatedly
intervalTillNextCron = Self.selfCheckCronInterval
specialIntervalTillNextCron = nil
case .failedRepeatedly:
logger.warn("Self-check continued failure.")
newSelfCheckState = nil
intervalTillNextCron = Self.selfCheckCronInterval
specialIntervalTillNextCron = nil
case .failedRepeatedlyAndWarned:
logger.warn("Self-check continued failure, already warned.")
@ -500,27 +439,54 @@ public final class KeyTransparencyManager {
} else {
nil
}
intervalTillNextCron = Self.selfCheckCronInterval
specialIntervalTillNextCron = nil
}
if let newSelfCheckState {
Self.setSelfCheckState(newSelfCheckState, tx: tx)
keyTransparencyStore.setSelfCheckState(newSelfCheckState, tx: tx)
}
// Tell Cron we last completed in the past such that we'll try again
// after the appropriate interval.
Self.selfCheckCronStore.setMostRecentDate(
dateProvider()
.addingTimeInterval(-Self.selfCheckCronInterval)
.addingTimeInterval(intervalTillNextCron),
jitter: intervalTillNextCron / Cron.jitterFactor,
keyTransparencyStore.setSelfCheckCronJobCompletedAt(
now: dateProvider(),
specialIntervalTillNextCron: specialIntervalTillNextCron,
tx: tx,
)
}
}
// MARK: - KeyTransparencyStore
public struct KeyTransparencyStore {
/// Keys for `kvStore`.
/// - Important
/// If you're adding a new key here, consider whether it should be wiped
/// when Key Transparency is disabled. See: `setIsEnabled`.
private enum KVStoreKeys {
/// Keys to a `Bool` representing whether or not KT is enabled.
static let isEnabled = "isEnabled"
/// Keys to a `SelfCheckState`'s raw value.
static let selfCheckState = "selfCheckState"
/// Keys to a `Bool` representing whether or not we should show
/// first-time education about KT.
static let shouldShowFirstTimeEducation = "shouldShowFirstTimeEducation"
/// Keys to an opaque LibSignalClient blob.
static let distinguishedTreeHead = "distinguishedTreeHead"
}
private let cronStore: CronStore
private let kvStore: NewKeyValueStore
private let logger: PrefixedLogger
public init() {
self.cronStore = CronStore(uniqueKey: .keyTransparencySelfCheck)
self.kvStore = NewKeyValueStore(collection: "KeyTransparency")
self.logger = PrefixedLogger(prefix: "[KT]")
}
// MARK: - Opt-out
public static func isEnabled(tx: DBReadTransaction) -> Bool {
public func isEnabled(tx: DBReadTransaction) -> Bool {
guard BuildFlags.KeyTransparency.enabled else {
return false
}
@ -528,7 +494,7 @@ public final class KeyTransparencyManager {
return kvStore.fetchValue(Bool.self, forKey: KVStoreKeys.isEnabled, tx: tx) ?? true
}
public static func setIsEnabled(_ isEnabled: Bool, tx: DBWriteTransaction) {
public func setIsEnabled(_ isEnabled: Bool, tx: DBWriteTransaction) {
logger.info("\(isEnabled)")
kvStore.writeValue(isEnabled, forKey: KVStoreKeys.isEnabled, tx: tx)
@ -536,7 +502,7 @@ public final class KeyTransparencyManager {
if !isEnabled {
kvStore.removeValue(forKey: KVStoreKeys.distinguishedTreeHead, tx: tx)
kvStore.removeValue(forKey: KVStoreKeys.selfCheckState, tx: tx)
selfCheckCronStore.setMostRecentDate(.distantPast, jitter: 0, tx: tx)
cronStore.setMostRecentDate(.distantPast, jitter: 0, tx: tx)
failIfThrows {
try KeyTransparencyRecord.deleteAll(tx.database)
}
@ -545,25 +511,109 @@ public final class KeyTransparencyManager {
// MARK: - First-time education
public static func shouldShowFirstTimeEducation(tx: DBReadTransaction) -> Bool {
public func shouldShowFirstTimeEducation(tx: DBReadTransaction) -> Bool {
return kvStore.fetchValue(Bool.self, forKey: KVStoreKeys.shouldShowFirstTimeEducation, tx: tx) ?? true
}
public static func setHasShownFirstTimeEducation(_ value: Bool, tx: DBWriteTransaction) {
public func setHasShownFirstTimeEducation(_ value: Bool, tx: DBWriteTransaction) {
kvStore.writeValue(value, forKey: KVStoreKeys.shouldShowFirstTimeEducation, tx: tx)
}
// MARK: -
// MARK: - SelfCheckState
fileprivate static func getLastDistinguishedTreeHead(tx: DBReadTransaction) -> Data? {
fileprivate enum SelfCheckState: Int64 {
case succeeded = 1
case failedOnce = 2
case failedRepeatedly = 3
case failedRepeatedlyAndWarned = 4
}
fileprivate func selfCheckState(tx: DBReadTransaction) -> SelfCheckState? {
return kvStore.fetchValue(
Int64.self,
forKey: KVStoreKeys.selfCheckState,
tx: tx,
)
.map { SelfCheckState(rawValue: $0)! }
}
fileprivate func setSelfCheckState(_ state: SelfCheckState, tx: DBWriteTransaction) {
kvStore.writeValue(state.rawValue, forKey: KVStoreKeys.selfCheckState, tx: tx)
}
public func shouldWarnSelfCheckFailed(tx: DBReadTransaction) -> Bool {
switch selfCheckState(tx: tx) {
case .failedRepeatedly:
return true
case nil, .succeeded, .failedOnce, .failedRepeatedlyAndWarned:
return false
}
}
public func setWarnedSelfCheckFailed(tx: DBWriteTransaction) {
switch selfCheckState(tx: tx) {
case .failedRepeatedly:
setSelfCheckState(.failedRepeatedlyAndWarned, tx: tx)
case nil, .succeeded, .failedOnce, .failedRepeatedlyAndWarned:
owsFailDebug("Unexpectedly setting warned, but shouldn't have warned?")
}
}
// MARK: - Self-check and Cron
private let selfCheckCronInterval: TimeInterval = if BuildFlags.KeyTransparency.conservativeSelfCheck {
.day
} else {
.week
}
fileprivate func getIsTimeForSelfCheckCronJob(
now: Date,
tx: DBReadTransaction,
) -> Bool {
let mostRecentDate = cronStore.mostRecentDate(tx: tx)
return now > mostRecentDate.addingTimeInterval(selfCheckCronInterval)
}
/// Set that the self-check `Cron` job just completed.
/// - Parameter specialIntervalTillNextCheck
/// If non-`nil`, indicates when the next `Cron` job should run. If `nil`,
/// the next `Cron` job will run at the default interval.
fileprivate func setSelfCheckCronJobCompletedAt(
now: Date,
specialIntervalTillNextCron: TimeInterval?,
tx: DBWriteTransaction,
) {
var mostRecentDate = now
// Cron tracks the most-recent date, not the next date. If we want to
// run at a specific future date, set the most-recent date in the past
// such that our next check will happen at that future interval.
if let specialIntervalTillNextCron {
mostRecentDate.addTimeInterval(-selfCheckCronInterval)
mostRecentDate.addTimeInterval(specialIntervalTillNextCron)
}
cronStore.setMostRecentDate(
mostRecentDate,
jitter: (specialIntervalTillNextCron ?? selfCheckCronInterval) / Cron.jitterFactor,
tx: tx,
)
}
// MARK: - LastDistinguishedTreeHead
fileprivate func getLastDistinguishedTreeHead(tx: DBReadTransaction) -> Data? {
return kvStore.fetchValue(Data.self, forKey: KVStoreKeys.distinguishedTreeHead, tx: tx)
}
fileprivate static func setLastDistinguishedTreeHead(_ blob: Data, tx: DBWriteTransaction) {
fileprivate func setLastDistinguishedTreeHead(_ blob: Data, tx: DBWriteTransaction) {
kvStore.writeValue(blob, forKey: KVStoreKeys.distinguishedTreeHead, tx: tx)
}
public static func getKeyTransparencyBlob(
// MARK: - LibSignal blobs
public func getKeyTransparencyBlob(
aci: Aci,
tx: DBReadTransaction,
) -> Data? {
@ -572,7 +622,7 @@ public final class KeyTransparencyManager {
}
}
public static func setKeyTransparencyBlob(
public func setKeyTransparencyBlob(
_ libsignalBlob: Data,
aci: Aci,
tx: DBWriteTransaction,
@ -594,33 +644,34 @@ public final class KeyTransparencyManager {
/// exclusively when calling LibSignal's KT APIs.
private struct KeyTransparencyStoreForLibSignal: KeyTransparency.Store {
let db: DB
let keyTransparencyStore: KeyTransparencyStore
func getLastDistinguishedTreeHead() async -> Data? {
db.read { tx in
KeyTransparencyManager.getLastDistinguishedTreeHead(tx: tx)
keyTransparencyStore.getLastDistinguishedTreeHead(tx: tx)
}
}
func setLastDistinguishedTreeHead(to blob: Data) async {
await db.awaitableWrite { tx in
KeyTransparencyManager.setLastDistinguishedTreeHead(blob, tx: tx)
keyTransparencyStore.setLastDistinguishedTreeHead(blob, tx: tx)
}
}
func getAccountData(for aci: Aci) async -> Data? {
db.read { tx in
KeyTransparencyManager.getKeyTransparencyBlob(aci: aci, tx: tx)
keyTransparencyStore.getKeyTransparencyBlob(aci: aci, tx: tx)
}
}
func setAccountData(_ data: Data, for aci: Aci) async {
await db.awaitableWrite { tx in
KeyTransparencyManager.setKeyTransparencyBlob(data, aci: aci, tx: tx)
keyTransparencyStore.setKeyTransparencyBlob(data, aci: aci, tx: tx)
}
}
}
// MARK: -
// MARK: - KeyTransparencyRecord
private struct KeyTransparencyRecord: Codable, FetchableRecord, PersistableRecord {
static let databaseTableName: String = "KeyTransparency"

View File

@ -28,6 +28,7 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
let db = DependenciesBridge.shared.db
let identityManager = DependenciesBridge.shared.identityManager
let keyTransparencyManager = DependenciesBridge.shared.keyTransparencyManager
let keyTransparencyStore = KeyTransparencyStore()
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
let fingerprintResult: FingerprintResult?
@ -55,13 +56,13 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
return (nil, nil, false)
}
let keyTransparencyIsEnabled = KeyTransparencyManager.isEnabled(tx: tx)
let keyTransparencyIsEnabled = keyTransparencyStore.isEnabled(tx: tx)
let keyTransparencyCheckParams = keyTransparencyManager.prepareCheck(
aci: theirAci,
localIdentifiers: localIdentifiers,
tx: tx,
)
let keyTransparencyShouldShowEducation = KeyTransparencyManager.shouldShowFirstTimeEducation(tx: tx)
let keyTransparencyShouldShowEducation = keyTransparencyStore.shouldShowFirstTimeEducation(tx: tx)
return (
FingerprintResult(
@ -120,7 +121,7 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
if keyTransparencyShouldShowEducation {
let educationSheet = KeyTransparencyFirstTimeEducationHeroSheet {
db.write { tx in
KeyTransparencyManager.setHasShownFirstTimeEducation(true, tx: tx)
keyTransparencyStore.setHasShownFirstTimeEducation(true, tx: tx)
}
viewController.present(navigationController, animated: true)