Add KeyTransparencyStore, instead of static methods on KeyTransparencyManager
This commit is contained in:
parent
0703b073c3
commit
e4e9d7013f
@ -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)
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user