ios: check database encryption for backup at launch, re-apply backup rule after restoring database backup

This commit is contained in:
Narasimha-sc committed 2026-10-01 22:59:28 +00:00
1 parent ba8ec796df
commit 51ff6ef915
4 files changed
+6 -4

No files matched your search

@@ -263,6 +263,7 @@ struct DatabaseErrorView: View {
private func restoreDb() {
do {
try restoreBackup()
updateAppDataBackup()
showRestoreDbButton = false
encryptionStartedDefault.set(false)
} catch {
@@ -62,7 +62,8 @@ func updateAppDataBackup() {
@MainActor
func updateAppDataBackupOnLaunch() {
if UIApplication.shared.isProtectedDataAvailable {
excludeAppDataFromBackup(!iCloudBackupDefault.get() || dbContainerGroupDefault.get() != .group || initialRandomDBPassphraseGroupDefault.get())
let dbEncrypted = !storeDBPassphraseGroupDefault.get() || kcDatabasePassword.get()?.isEmpty == false
excludeAppDataFromBackup(!iCloudBackupDefault.get() || iCloudBackupBlock(dbEncrypted) != nil)
}
}
+1 -1
View File
@@ -80,7 +80,7 @@ The container choice is stored in `dbContainerGroupDefault` (`GroupDefaults`).
The app group container is included in backup when the "Enable iCloud backup" toggle in "Database passphrase & export" is on (`DEFAULT_ICLOUD_BACKUP`, on by default) and [`iCloudBackupBlock`](../Shared/Views/Database/DatabaseView.swift) returns `nil`: the database is in the app group container, encrypted, and its passphrase was set by the user, not the initial random one. Otherwise the toggle is disabled and shown off, and its footer states the reason. The keychain passphrase is stored with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, so on another device the user has to enter it after a restore.
[`updateAppDataBackup()`](../Shared/Views/Database/DatabaseView.swift) applies the rule in [`initializeChat`](../Shared/Model/SimpleXAPI.swift) after the database is opened and the file paths are set (the core re-creates `temp_files` then), after a passphrase change in [`DatabaseEncryptionView`](../Shared/Views/Database/DatabaseEncryptionView.swift) (the core makes new `.bak` copies), and when the toggle changes. [`prepareForLaunch()`](../Shared/AppDelegate.swift#L124-L127) applies it with `updateAppDataBackupOnLaunch()` without the encryption check, as the database is not open yet; while protected data is unavailable it does nothing, keeping the last applied state.
[`updateAppDataBackup()`](../Shared/Views/Database/DatabaseView.swift) applies the rule in [`initializeChat`](../Shared/Model/SimpleXAPI.swift) after the database is opened and the file paths are set (the core re-creates `temp_files` then), after a passphrase change in [`DatabaseEncryptionView`](../Shared/Views/Database/DatabaseEncryptionView.swift) (the core makes new `.bak` copies), after restoring the database backup in [`DatabaseErrorView`](../Shared/Views/Database/DatabaseErrorView.swift), and when the toggle changes. [`prepareForLaunch()`](../Shared/AppDelegate.swift#L124-L127) applies it with `updateAppDataBackupOnLaunch()` before the database is opened, treating it as encrypted when its passphrase is not stored in the keychain (it was set by the user) or a key is stored there, as `chatMigrateInit` opens the database with the stored key (importing an archive removes it, so an unencrypted imported database is excluded); while protected data is unavailable it does nothing, keeping the last applied state.
Accepted gaps: after deleting or importing a database, the previous decision stays until the app restarts; `.bak` copies made during a passphrase change or migration, and `temp_files` re-created by the NSE or SE, are excluded only at the next update; files in `app_files` are backed up as stored (unencrypted when "Encrypt local files" is off).
+2 -2
View File
@@ -22,9 +22,9 @@ The app group container is included in backup when the toggle is on and `iCloudB
| the database is not encrypted | Database is not encrypted. Set passphrase to enable iCloud backup. |
| the database uses the initial random passphrase | Database is encrypted using a random passphrase. Set passphrase to enable iCloud backup. |
`excludeAppDataFromBackup(_:)` always excludes the Documents directory (legacy database, exported archives, migration files), `temp_files` (decrypted copies of files) and the `.bak` copies of the database (made by the core on passphrase changes and migrations, possibly unencrypted or with an old passphrase). It clears the attribute on the database files, because restoring a backup in `DatabaseErrorView` copies them from the excluded `.bak` files with `FileManager.copyItem`, which keeps the attribute.
`excludeAppDataFromBackup(_:)` always excludes the Documents directory (legacy database, exported archives, migration files), `temp_files` (decrypted copies of files) and the `.bak` copies of the database (made by the core on passphrase changes and migrations, possibly unencrypted or with an old passphrase). It clears the attribute on the database files, because restoring a backup in `DatabaseErrorView` copies them from the excluded `.bak` files with `FileManager.copyItem`, which keeps the attribute; the rule is applied right after the restore.
The rule is applied when the database is opened (after the file paths are set, as the core re-creates `temp_files`), after a passphrase change, when the toggle changes, and at launch from the stored flags (skipped while the device is locked, as they cannot be read).
The rule is applied when the database is opened (after the file paths are set, as the core re-creates `temp_files`), after a passphrase change, when the toggle changes, and at launch, before the database is opened: it is treated as encrypted when the passphrase is not stored in the keychain (it was set by the user) or a key is stored there, as `chatMigrateInit` opens it with that key — importing an archive removes the stored key, so an unencrypted import stays excluded even if the database then fails to open. This is skipped while the device is locked, as the stored flags cannot be read.
## Accepted gaps