Only register the Backup public key once
This commit is contained in:
parent
a97cff62d5
commit
90c176502e
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ public protocol BackupArchiveManager {
|
||||
func uploadEncryptedBackup(
|
||||
backupKey: MessageRootBackupKey,
|
||||
metadata: Upload.EncryptedBackupUploadMetadata,
|
||||
registeredBackupKeyToken: RegisteredBackupKeyToken,
|
||||
auth: ChatServiceAuth,
|
||||
progress: OWSProgressSink?,
|
||||
) async throws -> Upload.Result<Upload.EncryptedBackupUploadMetadata>
|
||||
|
||||
@ -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<Upload.EncryptedBackupUploadMetadata> {
|
||||
|
||||
@ -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<Upload.EncryptedBackupUploadMetadata> {
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
@ -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)?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user