Add first-time education sheet for Key Transparency
This commit is contained in:
parent
3cfa87b143
commit
2419e4d753
6
Signal/Images.xcassets/safety-numbers/Contents.json
Normal file
6
Signal/Images.xcassets/safety-numbers/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
Signal/Images.xcassets/safety-numbers/safety-number-verification.imageset/Contents.json
vendored
Normal file
12
Signal/Images.xcassets/safety-numbers/safety-number-verification.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "safety-number-verification.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@ -19,6 +19,11 @@ class DebugUIPrompts: DebugUIPage {
|
||||
var items = [OWSTableItem]()
|
||||
|
||||
items += [
|
||||
OWSTableItem(title: "Reenable KT first-time education", actionBlock: {
|
||||
db.write { tx in
|
||||
KeyTransparencyManager.setHasShownFirstTimeEducation(false, tx: tx)
|
||||
}
|
||||
}),
|
||||
|
||||
OWSTableItem(title: "Reenable disabled inactive linked device reminder megaphones", actionBlock: {
|
||||
db.write { tx in
|
||||
|
||||
@ -7609,6 +7609,12 @@
|
||||
/* Title for a button while automatic key verification is ongoing. */
|
||||
"SAFETY_NUMBERS_AUTOMATIC_VERIFICATION_BUTTON_VERIFYING" = "Verifying Encryption…";
|
||||
|
||||
/* Body for a sheet introducing Key Transparency. */
|
||||
"SAFETY_NUMBERS_AUTOMATIC_VERIFICATION_EDUCATION_SHEET_BODY" = "For contacts you’re connected to by phone number, Signal can automatically confirm whether the connection is secure using a process called key transparency. For added security, you can still verify connections manually using a QR code or number.";
|
||||
|
||||
/* Title for a sheet introducing Key Transparency. */
|
||||
"SAFETY_NUMBERS_AUTOMATIC_VERIFICATION_EDUCATION_SHEET_TITLE" = "Signal can now automatically verify encryption";
|
||||
|
||||
/* Body for a sheet explaining that encryption auto-verification did not succeed. Embeds {{ 1: the contact's name }}. */
|
||||
"SAFETY_NUMBERS_AUTOMATIC_VERIFICATION_FAILURE_SHEET_BODY_FORMAT" = "Signal can no longer automatically verify the encryption for this chat. This is likely because %1$@ changed their phone number. Verify end-to-end encryption manually by comparing the numbers on the previous screen or scanning the code on their device.";
|
||||
|
||||
|
||||
@ -10,6 +10,25 @@ 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
|
||||
@ -253,20 +272,17 @@ public final class KeyTransparencyManager {
|
||||
case failedRepeatedlyAndWarned = 4
|
||||
}
|
||||
|
||||
private static let selfCheckKVStore = NewKeyValueStore(collection: "KT.SelfCheck")
|
||||
private static let selfCheckKVStoreKey = "state"
|
||||
|
||||
private static func selfCheckState(tx: DBReadTransaction) -> SelfCheckState? {
|
||||
return selfCheckKVStore.fetchValue(
|
||||
return kvStore.fetchValue(
|
||||
Int64.self,
|
||||
forKey: selfCheckKVStoreKey,
|
||||
forKey: KVStoreKeys.selfCheckState,
|
||||
tx: tx,
|
||||
)
|
||||
.map { SelfCheckState(rawValue: $0)! }
|
||||
}
|
||||
|
||||
private static func setSelfCheckState(_ state: SelfCheckState, tx: DBWriteTransaction) {
|
||||
selfCheckKVStore.writeValue(state.rawValue, forKey: selfCheckKVStoreKey, tx: tx)
|
||||
kvStore.writeValue(state.rawValue, forKey: KVStoreKeys.selfCheckState, tx: tx)
|
||||
}
|
||||
|
||||
public static func shouldWarnSelfCheckFailed(tx: DBReadTransaction) -> Bool {
|
||||
@ -504,43 +520,47 @@ public final class KeyTransparencyManager {
|
||||
|
||||
// MARK: - Opt-out
|
||||
|
||||
private static let isEnabledKVStore = NewKeyValueStore(collection: "KT.IsEnabled")
|
||||
private static let isEnabledKVStoreKey = "isEnabled"
|
||||
|
||||
public static func isEnabled(tx: DBReadTransaction) -> Bool {
|
||||
guard BuildFlags.KeyTransparency.enabled else {
|
||||
return false
|
||||
}
|
||||
|
||||
return isEnabledKVStore.fetchValue(Bool.self, forKey: isEnabledKVStoreKey, tx: tx) ?? true
|
||||
return kvStore.fetchValue(Bool.self, forKey: KVStoreKeys.isEnabled, tx: tx) ?? true
|
||||
}
|
||||
|
||||
public static func setIsEnabled(_ isEnabled: Bool, tx: DBWriteTransaction) {
|
||||
logger.info("\(isEnabled)")
|
||||
|
||||
isEnabledKVStore.writeValue(isEnabled, forKey: isEnabledKVStoreKey, tx: tx)
|
||||
kvStore.writeValue(isEnabled, forKey: KVStoreKeys.isEnabled, tx: tx)
|
||||
|
||||
if !isEnabled {
|
||||
selfCheckKVStore.removeAll(tx: tx)
|
||||
kvStore.removeValue(forKey: KVStoreKeys.distinguishedTreeHead, tx: tx)
|
||||
kvStore.removeValue(forKey: KVStoreKeys.selfCheckState, tx: tx)
|
||||
selfCheckCronStore.setMostRecentDate(.distantPast, jitter: 0, tx: tx)
|
||||
distinguishedTreeKVStore.removeAll(tx: tx)
|
||||
failIfThrows {
|
||||
try KeyTransparencyRecord.deleteAll(tx.database)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - First-time education
|
||||
|
||||
public static func shouldShowFirstTimeEducation(tx: DBReadTransaction) -> Bool {
|
||||
return kvStore.fetchValue(Bool.self, forKey: KVStoreKeys.shouldShowFirstTimeEducation, tx: tx) ?? true
|
||||
}
|
||||
|
||||
public static func setHasShownFirstTimeEducation(_ value: Bool, tx: DBWriteTransaction) {
|
||||
kvStore.writeValue(value, forKey: KVStoreKeys.shouldShowFirstTimeEducation, tx: tx)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private static let distinguishedTreeKVStore = NewKeyValueStore(collection: "KT.DistinguishedTree")
|
||||
private static let distinguishedTreeKVStoreKey = "head"
|
||||
|
||||
fileprivate static func getLastDistinguishedTreeHead(tx: DBReadTransaction) -> Data? {
|
||||
return distinguishedTreeKVStore.fetchValue(Data.self, forKey: distinguishedTreeKVStoreKey, tx: tx)
|
||||
return kvStore.fetchValue(Data.self, forKey: KVStoreKeys.distinguishedTreeHead, tx: tx)
|
||||
}
|
||||
|
||||
fileprivate static func setLastDistinguishedTreeHead(_ blob: Data, tx: DBWriteTransaction) {
|
||||
distinguishedTreeKVStore.writeValue(blob, forKey: distinguishedTreeKVStoreKey, tx: tx)
|
||||
kvStore.writeValue(blob, forKey: KVStoreKeys.distinguishedTreeHead, tx: tx)
|
||||
}
|
||||
|
||||
fileprivate static func getKeyTransparencyRecord(
|
||||
|
||||
@ -32,12 +32,14 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
|
||||
|
||||
let fingerprintResult: FingerprintResult?
|
||||
let keyTransparencyState: KeyTransparencyState?
|
||||
let keyTransparencyShouldShowEducation: Bool
|
||||
(
|
||||
fingerprintResult,
|
||||
keyTransparencyState,
|
||||
keyTransparencyShouldShowEducation,
|
||||
) = db.read { tx in
|
||||
guard let theirAci else {
|
||||
return (nil, nil)
|
||||
return (nil, nil, false)
|
||||
}
|
||||
|
||||
let theirAddress = SignalServiceAddress(theirAci)
|
||||
@ -50,7 +52,7 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
|
||||
let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx),
|
||||
let myAciIdentityKey = identityManager.identityKeyPair(for: .aci, tx: tx)?.keyPair.identityKey
|
||||
else {
|
||||
return (nil, nil)
|
||||
return (nil, nil, false)
|
||||
}
|
||||
|
||||
let keyTransparencyIsEnabled = KeyTransparencyManager.isEnabled(tx: tx)
|
||||
@ -59,6 +61,7 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
|
||||
localIdentifiers: localIdentifiers,
|
||||
tx: tx,
|
||||
)
|
||||
let keyTransparencyShouldShowEducation = KeyTransparencyManager.shouldShowFirstTimeEducation(tx: tx)
|
||||
|
||||
return (
|
||||
FingerprintResult(
|
||||
@ -78,6 +81,7 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
|
||||
checkParams: keyTransparencyCheckParams,
|
||||
viewInitialState: keyTransparencyCheckParams == nil ? .unableToVerify : .readyToVerify,
|
||||
),
|
||||
keyTransparencyShouldShowEducation,
|
||||
)
|
||||
}
|
||||
|
||||
@ -112,7 +116,19 @@ public class FingerprintViewController: OWSViewController, OWSNavigationChildCon
|
||||
),
|
||||
)
|
||||
let navigationController = OWSNavigationController(rootViewController: fingerprintViewController)
|
||||
viewController.present(navigationController, animated: true)
|
||||
|
||||
if keyTransparencyShouldShowEducation {
|
||||
let educationSheet = KeyTransparencyFirstTimeEducationHeroSheet {
|
||||
db.write { tx in
|
||||
KeyTransparencyManager.setHasShownFirstTimeEducation(true, tx: tx)
|
||||
}
|
||||
|
||||
viewController.present(navigationController, animated: true)
|
||||
}
|
||||
viewController.present(educationSheet, animated: true)
|
||||
} else {
|
||||
viewController.present(navigationController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
@ -846,6 +862,32 @@ private final class KeyTransparencyFailureHeroSheet: HeroSheetViewController {
|
||||
|
||||
// MARK: -
|
||||
|
||||
private final class KeyTransparencyFirstTimeEducationHeroSheet: HeroSheetViewController {
|
||||
init(onContinue: @MainActor @escaping () -> Void) {
|
||||
super.init(
|
||||
hero: .image(UIImage(named: "safety-number-verification")!),
|
||||
title: OWSLocalizedString(
|
||||
"SAFETY_NUMBERS_AUTOMATIC_VERIFICATION_EDUCATION_SHEET_TITLE",
|
||||
comment: "Title for a sheet introducing Key Transparency.",
|
||||
),
|
||||
body: OWSLocalizedString(
|
||||
"SAFETY_NUMBERS_AUTOMATIC_VERIFICATION_EDUCATION_SHEET_BODY",
|
||||
comment: "Body for a sheet introducing Key Transparency.",
|
||||
),
|
||||
primaryButton: HeroSheetViewController.Button(
|
||||
title: CommonStrings.continueButton,
|
||||
action: { sheet in
|
||||
sheet.dismiss(animated: true) {
|
||||
onContinue()
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
#if DEBUG
|
||||
|
||||
private extension IdentityKey {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user