From 905df659d3f397c23d0c43ea0f3727c0dd65cbb7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 5 Oct 2026 09:46:55 +0100 Subject: [PATCH] ios: show error when random database passphrase generation fails (#7621) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../Shared/Views/Database/DatabaseErrorView.swift | 5 +++++ .../Shared/Views/Migration/MigrateFromDevice.swift | 2 ++ .../Shared/Views/Migration/MigrateToDevice.swift | 4 +++- apps/ios/SimpleX SE/ShareModel.swift | 5 +++++ apps/ios/SimpleXChat/API.swift | 14 ++++++++++---- apps/ios/SimpleXChat/KeyChain.swift | 4 ++-- apps/ios/SimpleXChat/Notifications.swift | 2 +- apps/ios/spec/database.md | 1 + 8 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift index f7f253a617..4c4df931cb 100644 --- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift @@ -118,6 +118,9 @@ struct DatabaseErrorView: View { case .errorKeychain: titleText("Keychain error") errorView(Text("Cannot access keychain to save database password")) + case .errorKeyGeneration: + titleText("Database error") + errorView(Text("Cannot generate random database passphrase")) case .invalidConfirmation: // this can only happen if incorrect parameter is passed titleText("Invalid migration confirmation") @@ -217,6 +220,8 @@ struct DatabaseErrorView: View { ) case .errorKeychain: am.showAlertMsg(title: "Keychain error") + case .errorKeyGeneration: + am.showAlertMsg(title: "Database error", message: "Cannot generate random database passphrase") case let .errorSQL(_, error): am.showAlert(Alert( title: Text("Database error"), diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 2ff376701c..c262d1165a 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -709,6 +709,8 @@ private func showErrorOnMigrationIfNeeded(_ status: DBMigrationResult, _ alert: alert.wrappedValue = .wrongPassphrase() case .errorKeychain: alert.wrappedValue = .keychainError() + case .errorKeyGeneration: + alert.wrappedValue = .databaseError(message: NSLocalizedString("Cannot generate random database passphrase", comment: "alert message")) case let .errorSQL(_, error): alert.wrappedValue = .databaseError(message: error) case let .unknown(error): diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index cb3832b727..4181f6e73f 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -539,7 +539,7 @@ struct MigrateToDevice: View { Task { do { if !hasChatCtrl() { - chatInitControllerRemovingDatabases() + try chatInitControllerRemovingDatabases() } else if ChatModel.shared.chatRunning == true { // cannot delete storage if chat is running try await stopChatAsync() @@ -735,6 +735,8 @@ private func showErrorOnMigrationIfNeeded(_ status: DBMigrationResult, _ alert: alert.wrappedValue = .wrongPassphrase() case .errorKeychain: alert.wrappedValue = .keychainError() + case .errorKeyGeneration: + alert.wrappedValue = .databaseError(message: NSLocalizedString("Cannot generate random database passphrase", comment: "alert message")) case let .errorSQL(_, error): alert.wrappedValue = .databaseError(message: error) case let .unknown(error): diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index ea1bbbd46f..bd0354ce51 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -247,6 +247,11 @@ class ShareModel: ObservableObject { title: "Keychain error", message: "Cannot access keychain to save database password" ) + case .errorKeyGeneration: + ErrorAlert( + title: "Database error", + message: "Cannot generate random database passphrase" + ) case .invalidConfirmation: ErrorAlert("Invalid migration confirmation") case let .unknown(json): diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index bc23e53011..3b860d524b 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -32,7 +32,12 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio } else if useKeychain { if !hasDatabase() { logger.debug("chatMigrateInit generating a random DB key") - dbKey = randomDatabasePassword() + guard let key = randomDatabasePassword() else { + let result = (false, DBMigrationResult.errorKeyGeneration) + migrationResult = result + return result + } + dbKey = key initialRandomDBPassphraseGroupDefault.set(true) } else if let key = kcDatabasePassword.get() { dbKey = key @@ -56,7 +61,7 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation: MigrationConfirmation = .error) -> (DBMigrationResult, chat_ctrl?) { let dbPath = url.path - let dbKey = key ?? randomDatabasePassword() + guard let dbKey = key ?? randomDatabasePassword() else { return (.errorKeyGeneration, nil) } logger.debug("chatInitTemporaryDatabase path: \(dbPath)") var temporaryController: chat_ctrl? = nil var cPath = dbPath.cString(using: .utf8)! @@ -66,14 +71,14 @@ public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation return (dbMigrationResult(dataFromCString(cjson)), temporaryController) } -public func chatInitControllerRemovingDatabases() { +public func chatInitControllerRemovingDatabases() throws { let dbPath = getAppDatabasePath().path let fm = FileManager.default // Remove previous databases, otherwise, can be .errorNotADatabase with nil controller try? fm.removeItem(atPath: dbPath + CHAT_DB) try? fm.removeItem(atPath: dbPath + AGENT_DB) - let dbKey = randomDatabasePassword() + guard let dbKey = randomDatabasePassword() else { throw RuntimeError("Cannot generate random database passphrase") } logger.debug("chatInitControllerRemovingDatabases path: \(dbPath)") var cPath = dbPath.cString(using: .utf8)! var cKey = dbKey.cString(using: .utf8)! @@ -353,6 +358,7 @@ public enum DBMigrationResult: Decodable, Equatable { case errorMigration(dbFile: String, migrationError: MigrationError) case errorSQL(dbFile: String, migrationSQLError: String) case errorKeychain + case errorKeyGeneration case unknown(json: String) } diff --git a/apps/ios/SimpleXChat/KeyChain.swift b/apps/ios/SimpleXChat/KeyChain.swift index 50a5946462..7806595ecf 100644 --- a/apps/ios/SimpleXChat/KeyChain.swift +++ b/apps/ios/SimpleXChat/KeyChain.swift @@ -37,7 +37,7 @@ public struct KeyChainItem { } } -func randomDatabasePassword() -> String { +func randomDatabasePassword() -> String? { var keyData = Data(count: 32) let status = keyData.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!) @@ -46,7 +46,7 @@ func randomDatabasePassword() -> String { return keyData.base64EncodedString() } else { logger.error("randomDatabasePassword: error \(status)") - return "" + return nil } } diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift index a40e8eda99..552805e381 100644 --- a/apps/ios/SimpleXChat/Notifications.swift +++ b/apps/ios/SimpleXChat/Notifications.swift @@ -138,7 +138,7 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) -> title = NSLocalizedString("Encrypted message: no passphrase", comment: "notification") case .errorMigration: title = NSLocalizedString("Encrypted message: database migration error", comment: "notification") - case .errorSQL: + case .errorSQL, .errorKeyGeneration: title = NSLocalizedString("Encrypted message: database error", comment: "notification") case .errorKeychain: title = NSLocalizedString("Encrypted message: keychain error", comment: "notification") diff --git a/apps/ios/spec/database.md b/apps/ios/spec/database.md index cb08abe2aa..14fee1cb1c 100644 --- a/apps/ios/spec/database.md +++ b/apps/ios/spec/database.md @@ -134,6 +134,7 @@ Migration results are decoded in Swift as `DBMigrationResult`: - `.errorMigration(dbFile:, migrationError:)` -- migration failed - `.errorSQL(dbFile:, migrationSQLError:)` -- SQL error during migration - `.errorKeychain` -- keychain access failed +- `.errorKeyGeneration` -- random database key generation failed - `.unknown(json:)` -- unrecognized response ---