Signal-iOS/SignalServiceKit/Util/OWS2FAManager.swift
2024-03-27 12:22:52 -05:00

259 lines
9.2 KiB
Swift

//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
extension OWS2FAManager {
@objc
public static var isRegistrationLockV2EnabledKey = "isRegistrationLockV2Enabled"
@objc
public var isRegistrationLockEnabled: Bool {
switch mode {
case .V2:
return isRegistrationLockV2Enabled
case .V1:
return true // In v1 reg lock and 2fa are the same thing.
case .disabled:
return false
}
}
@objc
public var isRegistrationLockV2Enabled: Bool {
return databaseStorage.read { transaction in
OWS2FAManager.keyValueStore().getBool(
OWS2FAManager.isRegistrationLockV2EnabledKey,
defaultValue: false,
transaction: transaction
)
}
}
public func isRegistrationLockV2Enabled(transaction: SDSAnyReadTransaction) -> Bool {
return OWS2FAManager.keyValueStore().getBool(
OWS2FAManager.isRegistrationLockV2EnabledKey,
defaultValue: false,
transaction: transaction
)
}
public func requestEnable2FA(withPin pin: String, mode: OWS2FAMode) -> Promise<Void> {
return Promise { future in
requestEnable2FA(withPin: pin, mode: mode, success: {
future.resolve()
}) { error in
future.reject(error)
}
}
}
@objc
@available(swift, obsoleted: 1.0)
public func enableRegistrationLockV2() -> AnyPromise {
return AnyPromise(enableRegistrationLockV2())
}
public func enableRegistrationLockV2() -> Promise<Void> {
return DispatchQueue.global().async(.promise) { () -> String in
let token = Self.databaseStorage.read { tx in
return DependenciesBridge.shared.svr.data(
for: .registrationLock,
transaction: tx.asV2Read
)?.canonicalStringRepresentation
}
guard let token else {
throw OWSAssertionError("Cannot enable registration lock without an existing PIN")
}
return token
}.then { token -> Promise<HTTPResponse> in
let request = OWSRequestFactory.enableRegistrationLockV2Request(token: token)
return self.networkManager.makePromise(request: request)
}.done { _ in
self.databaseStorage.write { transaction in
OWS2FAManager.keyValueStore().setBool(
true,
key: OWS2FAManager.isRegistrationLockV2EnabledKey,
transaction: transaction
)
}
firstly {
return Promise.wrapAsync {
try await DependenciesBridge.shared.accountAttributesUpdater.updateAccountAttributes(authedAccount: .implicit())
}
}.catch { error in
Logger.error("Error: \(error)")
}
}
}
public func markRegistrationLockV2Enabled(transaction: SDSAnyWriteTransaction) {
guard !DependenciesBridge.shared.tsAccountManager.registrationState(tx: transaction.asV2Read).isRegistered else {
return owsFailDebug("Unexpectedly attempted to mark reglock as enabled after registration")
}
OWS2FAManager.keyValueStore().setBool(
true,
key: OWS2FAManager.isRegistrationLockV2EnabledKey,
transaction: transaction
)
}
@objc
@available(swift, obsoleted: 1.0)
public func disableRegistrationLockV2() -> AnyPromise {
return AnyPromise(disableRegistrationLockV2())
}
public func disableRegistrationLockV2() -> Promise<Void> {
return firstly { () -> Promise<HTTPResponse> in
let request = OWSRequestFactory.disableRegistrationLockV2Request()
return self.networkManager.makePromise(request: request)
}.done { _ in
self.databaseStorage.write { transaction in
OWS2FAManager.keyValueStore().removeValue(
forKey: OWS2FAManager.isRegistrationLockV2EnabledKey,
transaction: transaction
)
}
firstly {
return Promise.wrapAsync {
try await DependenciesBridge.shared.accountAttributesUpdater.updateAccountAttributes(authedAccount: .implicit())
}
}.catch { error in
Logger.error("Error: \(error)")
}
}
}
public func markRegistrationLockV2Disabled(transaction: SDSAnyWriteTransaction) {
guard !DependenciesBridge.shared.tsAccountManager.registrationState(tx: transaction.asV2Read).isRegistered else {
return owsFailDebug("Unexpectedly attempted to mark reglock as disabled after registration")
}
OWS2FAManager.keyValueStore().removeValue(
forKey: OWS2FAManager.isRegistrationLockV2EnabledKey,
transaction: transaction
)
}
@objc
@available(swift, obsoleted: 1.0)
public func migrateToRegistrationLockV2() -> AnyPromise {
return AnyPromise(migrateToRegistrationLockV2())
}
public func migrateToRegistrationLockV2() -> Promise<Void> {
guard let pinCode = pinCode else {
return Promise(error: OWSAssertionError("tried to migrate to registration lock V2 without legacy PIN"))
}
return firstly {
return requestEnable2FA(withPin: pinCode, mode: .V2)
}.then {
return self.enableRegistrationLockV2()
}
}
@objc
public func enable2FAV1(pin: String,
success: (() -> Void)?,
failure: ((Error) -> Void)?) {
// Convert the pin to arabic numerals, we never want to
// operate with pins in other numbering systems.
let request = OWSRequestFactory.enable2FARequest(withPin: pin.ensureArabicNumerals)
firstly {
Self.networkManager.makePromise(request: request)
}.done(on: DispatchQueue.main) { _ in
Self.databaseStorage.write { transaction in
self.markEnabled(pin: pin, transaction: transaction)
}
success?()
}.catch(on: DispatchQueue.main) { error in
owsFailDebugUnlessNetworkFailure(error)
failure?(error)
}
}
@objc
public func disable2FAV1(success: OWS2FASuccess?,
failure: OWS2FAFailure?) {
let request = OWSRequestFactory.disable2FARequest()
firstly {
Self.networkManager.makePromise(request: request)
}.done(on: DispatchQueue.main) { _ in
Self.databaseStorage.write { transaction in
self.markDisabled(transaction: transaction)
}
success?()
}.catch(on: DispatchQueue.main) { error in
owsFailDebugUnlessNetworkFailure(error)
failure?(error)
}
}
public static func isWeakPin(_ pin: String) -> Bool {
let normalizedPin = SVRUtil.normalizePin(pin)
guard pin.count >= kMin2FAv2PinLength else { return true }
// We only check numeric pins for weakness
guard normalizedPin.digitsOnly() == normalizedPin else { return false }
var allTheSame = true
var forwardSequential = true
var reverseSequential = true
var previousWholeNumberValue: Int?
for character in normalizedPin {
guard let current = character.wholeNumberValue else {
owsFailDebug("numeric pin unexpectedly contatined non-numeric characters")
break
}
defer { previousWholeNumberValue = current }
guard let previous = previousWholeNumberValue else { continue }
if previous != current { allTheSame = false }
if previous + 1 != current { forwardSequential = false }
if previous - 1 != current { reverseSequential = false }
if !allTheSame && !forwardSequential && !reverseSequential { break }
}
return allTheSame || forwardSequential || reverseSequential
}
@objc
public func clearLocalPinCode(transaction: SDSAnyWriteTransaction) {
Self.keyValueStore().removeValue(forKey: kOWS2FAManager_PinCode, transaction: transaction)
}
// MARK: - KeyBackupService Wrappers/Helpers
@objc
public func hasBackedUpMasterKey(transaction: SDSAnyReadTransaction) -> Bool {
return DependenciesBridge.shared.svr.hasBackedUpMasterKey(transaction: transaction.asV2Read)
}
@objc(generateAndBackupKeysWithPin:)
@available(swift, obsoleted: 1.0)
public func generateAndBackupKeys(with pin: String) -> AnyPromise {
let promise = DependenciesBridge.shared.svr.generateAndBackupKeys(pin: pin, authMethod: .implicit)
return AnyPromise(promise)
}
@objc
public func verifyKBSPin(_ pin: String, resultHandler: @escaping (Bool) -> Void) {
DependenciesBridge.shared.svr.verifyPin(pin, resultHandler: resultHandler)
}
@objc(deleteKeys)
public func deleteKBSKeys() -> AnyPromise {
return AnyPromise(DependenciesBridge.shared.svr.deleteKeys())
}
}