diff --git a/Signal/Backups/BackupSettingsViewController.swift b/Signal/Backups/BackupSettingsViewController.swift index 5f06f99f36..9c4d91362c 100644 --- a/Signal/Backups/BackupSettingsViewController.swift +++ b/Signal/Backups/BackupSettingsViewController.swift @@ -1537,7 +1537,7 @@ private struct BackupExportProgressView: View { private var progressBarState: ProgressBarState { switch latestExportProgressUpdate.currentStep { - case .registerBackupId, .backupExport, .backupUpload: + case .backupExport, .backupUpload: let percentExportCompleted = latestExportProgressUpdate.progress(for: .backupExport)?.percentComplete ?? 0 let percentUploadCompleted = latestExportProgressUpdate.progress(for: .backupUpload)?.percentComplete ?? 0 let percentComplete = (0.95 * percentExportCompleted) + (0.05 * percentUploadCompleted) diff --git a/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift index a8a9e49275..470deca614 100644 --- a/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift @@ -137,8 +137,10 @@ class InternalSettingsViewController: OWSTableViewController2 { } if mode != .registration { - backupsSection.add(.actionItem(withText: "Export + Validate Message Backup proto") { - self.exportMessageBackupProto() + backupsSection.add(.actionItem(withText: "Export + Validate Message Backup proto") { [self] in + Task { + await exportMessageBackupProto() + } }) } if FeatureFlags.Backups.showOptimizeMedia { @@ -368,152 +370,69 @@ private extension InternalSettingsViewController { SpinningCheckmarks.shouldSpin = !wasSpinning } - func exportMessageBackupProto() { - ModalActivityIndicatorViewController.present( - fromViewController: self, - canCancel: false - ) { modal in - func dismissModalAndToast(_ message: String) { - DependenciesBridge.shared.backupArchiveErrorPresenter.presentOverTopmostViewController(completion: { - modal.dismiss { - self.presentToast(text: message) - } - }) - } - - func exportMessageBackupProtoFile() { - Task { - let result = await Result(catching: { - try await self.exportMessageBackupProtoFile(presentingFrom: modal) - }) - await MainActor.run { - switch result { - case .success: - dismissModalAndToast("Success! Key copied to clipboard") - case .failure(let error): - dismissModalAndToast("Failed! \(error.localizedDescription)") - } - } - } - } - - DispatchQueue.main.async { - let actionSheet = ActionSheetController(title: "Choose backup destination:") - - let localFileAction = ActionSheetAction(title: "Local device") { _ in - exportMessageBackupProtoFile() - } - - let remoteFileAction = ActionSheetAction(title: "Remote server") { _ in - Task { - let result = await Result(catching: { - try await self.exportMessageBackupProtoRemotely() - }) - await MainActor.run { - switch result { - case .success: - dismissModalAndToast("Done") - case .failure(let error): - dismissModalAndToast("Failed! \(error.localizedDescription)") - } - } - } - } - - actionSheet.addAction(localFileAction) - actionSheet.addAction(remoteFileAction) - modal.presentActionSheet(actionSheet) - } - } - } - - // Right now this "local" backup uses the same format and encryption scheme - // as the remote backup. In the future, this should use the local backup - // format and encryption scheme. - func exportMessageBackupProtoFile( - presentingFrom vc: UIViewController - ) async throws { + func exportMessageBackupProto() async { let accountKeyStore = DependenciesBridge.shared.accountKeyStore let backupArchiveManager = DependenciesBridge.shared.backupArchiveManager + let db = DependenciesBridge.shared.db + let errorPresenter = DependenciesBridge.shared.backupArchiveErrorPresenter let tsAccountManager = DependenciesBridge.shared.tsAccountManager - let (messageBackupKey, localIdentifiers) = try SSKEnvironment.shared.databaseStorageRef.read { tx in - let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx)! - return ( - try accountKeyStore.getMessageRootBackupKey(aci: localIdentifiers.aci, tx: tx), - localIdentifiers - ) + func presentBackupErrorsAndToast(_ message: String) { + errorPresenter.presentOverTopmostViewController { [self] in + presentToast(text: message) + } } - guard let messageBackupKey else { - return - } + let backupKey: MessageBackupKey + let exportMetadata: Upload.EncryptedBackupUploadMetadata + do { + (backupKey, exportMetadata) = try await ModalActivityIndicatorViewController.presentAndPropagateResult( + from: self + ) { + let (messageBackupKey, localIdentifiers) = try db.read { tx in + let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx)! + return ( + try accountKeyStore.getMessageRootBackupKey(aci: localIdentifiers.aci, tx: tx)!, + localIdentifiers, + ) + } - let backupEncryptionKey = try MessageBackupKey( - backupKey: messageBackupKey.backupKey, - backupId: messageBackupKey.backupId - ) - - let metadata = try await backupArchiveManager.exportEncryptedBackup( - localIdentifiers: localIdentifiers, - backupPurpose: .remoteExport(key: messageBackupKey, chatAuth: .implicit()), - progress: nil - ) - - let keyString = "AES key: \(backupEncryptionKey.aesKey.base64EncodedString())" - + "\nHMAC key: \(backupEncryptionKey.hmacKey.base64EncodedString())" - - await withCheckedContinuation { continuation in - DispatchQueue.main.async { - let activityVC = UIActivityViewController( - activityItems: [metadata.fileUrl], - applicationActivities: nil + let backupKey = try MessageBackupKey( + backupKey: messageBackupKey.backupKey, + backupId: messageBackupKey.backupId ) - activityVC.popoverPresentationController?.sourceView = self.view - activityVC.completionWithItemsHandler = { _, _, _, _ in - UIPasteboard.general.string = keyString - continuation.resume() - } - vc.present(activityVC, animated: true) + + let exportMetadata = try await backupArchiveManager.exportEncryptedBackup( + localIdentifiers: localIdentifiers, + backupPurpose: .remoteExport(key: messageBackupKey, chatAuth: .implicit()), + progress: nil + ) + + return (backupKey, exportMetadata) } - } - } - - func exportMessageBackupProtoRemotely() async throws { - let accountKeyStore = DependenciesBridge.shared.accountKeyStore - let backupArchiveManager = DependenciesBridge.shared.backupArchiveManager - let backupKeyService = DependenciesBridge.shared.backupKeyService - let tsAccountManager = DependenciesBridge.shared.tsAccountManager - - let (messageBackupKey, localIdentifiers) = try SSKEnvironment.shared.databaseStorageRef.read { tx in - let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx)! - return ( - try accountKeyStore.getMessageRootBackupKey(aci: localIdentifiers.aci, tx: tx), - localIdentifiers - ) - } - - guard let messageBackupKey else { + } catch { + owsFailDebug("Failed to export Backup proto! \(error)") + presentBackupErrorsAndToast("Failed to export Backup proto!") return } - let metadata = try await backupArchiveManager.exportEncryptedBackup( - localIdentifiers: localIdentifiers, - backupPurpose: .remoteExport(key: messageBackupKey, chatAuth: .implicit()), - progress: nil - ) + let keyString = "AES key: \(backupKey.aesKey.base64EncodedString())" + + "\nHMAC key: \(backupKey.hmacKey.base64EncodedString())" - let registeredBackupKeyToken = try await backupKeyService.registerBackupKey( - localIdentifiers: localIdentifiers, - auth: .implicit() + let activityVC = UIActivityViewController( + activityItems: [exportMetadata.fileUrl], + applicationActivities: nil ) + activityVC.popoverPresentationController?.sourceView = view + activityVC.completionWithItemsHandler = { _, _, _, _ in + UIPasteboard.general.setItems( + [[UIPasteboard.typeAutomatic: keyString]], + options: [.expirationDate: Date().addingTimeInterval(120)] + ) - _ = try await backupArchiveManager.uploadEncryptedBackup( - backupKey: messageBackupKey, - metadata: metadata, - registeredBackupKeyToken: registeredBackupKeyToken, - auth: .implicit(), - progress: nil, - ) + presentBackupErrorsAndToast("Success! Encryption key copied.") + } + + present(activityVC, animated: true) } } diff --git a/SignalServiceKit/Backups/Archiving/BackupArchiveManager.swift b/SignalServiceKit/Backups/Archiving/BackupArchiveManager.swift index 2129c3ef81..56aa575f3c 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchiveManager.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchiveManager.swift @@ -42,7 +42,6 @@ public protocol BackupArchiveManager { func uploadEncryptedBackup( backupKey: MessageRootBackupKey, metadata: Upload.EncryptedBackupUploadMetadata, - registeredBackupKeyToken: RegisteredBackupKeyToken, auth: ChatServiceAuth, progress: OWSProgressSink?, ) async throws -> Upload.Result diff --git a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift index 26c5c395a5..57f7d93230 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift @@ -188,7 +188,6 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { public func uploadEncryptedBackup( backupKey: MessageRootBackupKey, metadata: Upload.EncryptedBackupUploadMetadata, - registeredBackupKeyToken: RegisteredBackupKeyToken, auth: ChatServiceAuth, progress: OWSProgressSink? ) async throws -> Upload.Result { diff --git a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift index ffd24f216f..60fb9ece4b 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift @@ -30,7 +30,6 @@ open class BackupArchiveManagerMock: BackupArchiveManager { public func uploadEncryptedBackup( backupKey: MessageRootBackupKey, metadata: Upload.EncryptedBackupUploadMetadata, - registeredBackupKeyToken: RegisteredBackupKeyToken, auth: ChatServiceAuth, progress: OWSProgressSink?, ) async throws -> Upload.Result { diff --git a/SignalServiceKit/Backups/BackupExportJob/BackupExportJob.swift b/SignalServiceKit/Backups/BackupExportJob/BackupExportJob.swift index cc73276498..ccc119a5e2 100644 --- a/SignalServiceKit/Backups/BackupExportJob/BackupExportJob.swift +++ b/SignalServiceKit/Backups/BackupExportJob/BackupExportJob.swift @@ -6,7 +6,6 @@ import LibSignalClient public enum BackupExportJobStep: String, OWSSequentialProgressStep { - case registerBackupId case backupExport case backupUpload case listMedia @@ -18,11 +17,10 @@ public enum BackupExportJobStep: String, OWSSequentialProgressStep { /// a given step should take. public var progressUnitCount: UInt64 { switch self { - case .registerBackupId: 1 case .backupExport: 40 case .backupUpload: 10 case .listMedia: 5 - case .attachmentOrphaning: 2 + case .attachmentOrphaning: 3 case .attachmentUpload: 40 case .offloading: 2 } @@ -245,18 +243,6 @@ class BackupExportJobImpl: BackupExportJob { } do { - logger.info("Starting...") - - let registeredBackupKeyToken = try await withEstimatedProgressUpdates( - estimatedTimeToCompletion: 0.5, - progress: progress?.child(for: .registerBackupId).addSource(withLabel: "", unitCount: 1), - ) { [backupKeyService] in - try await backupKeyService.registerBackupKey( - localIdentifiers: localIdentifiers, - auth: .implicit() - ) - } - logger.info("Exporting backup...") let uploadMetadata = try await backupArchiveManager.exportEncryptedBackup( @@ -298,7 +284,6 @@ class BackupExportJobImpl: BackupExportJob { _ = try await backupArchiveManager.uploadEncryptedBackup( backupKey: backupKey, metadata: uploadMetadata, - registeredBackupKeyToken: registeredBackupKeyToken, auth: .implicit(), progress: progress?.child(for: .backupUpload), ) diff --git a/SignalServiceKit/Backups/Settings/BackupKeyService.swift b/SignalServiceKit/Backups/Settings/BackupKeyService.swift index 657aeb9805..a4c74583f3 100644 --- a/SignalServiceKit/Backups/Settings/BackupKeyService.swift +++ b/SignalServiceKit/Backups/Settings/BackupKeyService.swift @@ -5,11 +5,6 @@ import LibSignalClient -/// An opaque token returned after registering a BackupKey, which can be -/// required by APIs that require a BackupKey to have been previously registered -/// in order to succeed. -public struct RegisteredBackupKeyToken {} - /// Responsible for CRUD of the "BackupKey", which is an asymmetric key used to /// sign Backup auth credentials. /// @@ -23,7 +18,7 @@ public protocol BackupKeyService { func registerBackupKey( localIdentifiers: LocalIdentifiers, auth: ChatServiceAuth - ) async throws -> RegisteredBackupKeyToken + ) async throws /// De-initialize Backups by deleting a previously-registered BackupKey. /// This is effectively a "delete Backup" operation, as subsequent to this @@ -88,7 +83,7 @@ final class BackupKeyServiceImpl: BackupKeyService { func registerBackupKey( localIdentifiers: LocalIdentifiers, auth: ChatServiceAuth - ) async throws -> RegisteredBackupKeyToken { + ) async throws { try await _registerBackupKey( localIdentifiers: localIdentifiers, auth: auth, @@ -100,7 +95,7 @@ final class BackupKeyServiceImpl: BackupKeyService { localIdentifiers: LocalIdentifiers, auth: ChatServiceAuth, retryOnFail: Bool - ) async throws -> RegisteredBackupKeyToken { + ) async throws { let (messageBackupKey, mediaBackupKey) = try await db.awaitableWrite { tx in try rootBackupKeys(localIdentifiers: localIdentifiers, tx: tx) } @@ -125,9 +120,6 @@ final class BackupKeyServiceImpl: BackupKeyService { _ = try await networkManager.asyncRequest( .backupSetPublicKeyRequest(backupAuth: mediaBackupAuth) ) - - return RegisteredBackupKeyToken() - } catch SignalError.verificationFailed where retryOnFail { // This error is thrown if the backupID was never registered remotely. // We *should* set it above in registerBackupIDIfNecessary based on local state, @@ -258,8 +250,8 @@ private extension TSRequest { #if TESTABLE_BUILD class MockBackupKeyService: BackupKeyService { - func registerBackupKey(localIdentifiers: LocalIdentifiers, auth: ChatServiceAuth) async throws -> RegisteredBackupKeyToken { - return RegisteredBackupKeyToken() + func registerBackupKey(localIdentifiers: LocalIdentifiers, auth: ChatServiceAuth) async throws { + // Do nothing } var deleteBackupKeyMock: (() async throws -> Void)?