-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
realm crash: Realm file decryption failed #1111
Comments
My suspicion seems right realm/realm-swift#5615 |
Also, could this have been accidentally triggered? if ProcessInfo().arguments.contains(AppReset.reset.rawValue) {
AppReset.resetKeychain()
AppReset.resetUserDefaults()
} |
relevant code //
// KeyChainService.swift
// FlowCrypt
//
// Created by Anton Kharchevskyi on 25.11.2019.
// Copyright © 2017-present FlowCrypt a. s. All rights reserved.
//
import FlowCryptCommon
import Foundation
import Security
// keychain is used to generate and retrieve encryption key which is used to encrypt local DB
// it does not contain any actual data or keys other than the db encryption key
protocol KeyChainServiceType {
func getStorageEncryptionKey() throws -> Data
}
struct KeyChainService: KeyChainServiceType {
private static var logger = Logger.nested(in: Self.self, with: "Keychain")
// the prefix ensures that we use a different keychain index after deleting the app
// because keychain entries survive app uninstall
private static var keychainIndex: String = {
// todo - verify if this is indeed atomic (because static) or if there can be a race condition
let prefixStorageIndex = "indexSecureKeychainPrefix"
let storageEncryptionKeyIndexSuffix = "-indexStorageEncryptionKey"
if let storedPrefix = UserDefaults.standard.string(forKey: prefixStorageIndex) {
return storedPrefix + storageEncryptionKeyIndexSuffix
}
guard let randomBytes = CoreHost().getSecureRandomByteNumberArray(12) else {
fatalError("could not get secureKeychainPrefix random bytes")
}
let prefix = Data(randomBytes)
.base64EncodedString()
.replacingOccurrences(of: "[^A-Za-z0-9]+", with: "", options: [.regularExpression])
logger.logInfo("LocalStorage.secureKeychainPrefix generating new prefix")
UserDefaults.standard.set(prefix, forKey: prefixStorageIndex)
return prefix + storageEncryptionKeyIndexSuffix
}()
private let keyByteLen = 64
private func generateAndSaveStorageEncryptionKey() throws {
Self.logger.logInfo("generateAndSaveStorageEncryptionKey")
guard let randomBytes = CoreHost().getSecureRandomByteNumberArray(keyByteLen) else {
let message = "KeyChainService getSecureRandomByteNumberArray bytes are nil"
throw AppErr.general(message)
}
let key = Data(randomBytes)
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: KeyChainService.keychainIndex,
kSecValueData: key
]
let addOsStatus = SecItemAdd(query as CFDictionary, nil)
guard addOsStatus == noErr else {
let message = "KeyChainService SecItemAdd osStatus = \(addOsStatus), expected 'noErr'"
throw AppErr.general(message)
}
}
func getStorageEncryptionKey() throws -> Data {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: KeyChainService.keychainIndex,
kSecReturnData: kCFBooleanTrue!,
kSecMatchLimit: kSecMatchLimitOne
]
var keyFromKeychain: AnyObject?
let findOsStatus = SecItemCopyMatching(query as CFDictionary, &keyFromKeychain)
guard findOsStatus != errSecItemNotFound else {
try generateAndSaveStorageEncryptionKey() // saves new key to storage
return try getStorageEncryptionKey() // retries search
}
guard findOsStatus == noErr else {
let message = "KeyChainService SecItemCopyMatching status = \(findOsStatus), expected 'noErr'"
throw AppErr.general(message)
}
guard let validKey = keyFromKeychain as? Data else {
let message = "KeyChainService keyFromKeychain not usable as Data. Is nil?: \(keyFromKeychain == nil)"
throw AppErr.general(message)
}
guard validKey.count == keyByteLen else {
let message = "KeyChainService validKey.count != \(keyByteLen), instead is \(validKey.count)"
throw AppErr.general(message)
}
return validKey
}
} |
I'll refactor this to have more control over initialization of Realm. We seem to be initializing encrypted storage / realm all over the app, and many times with Instead I'll do it once during app startup, and then dependency-inject already initialized storage. Plus make the key chain parts a MainActor. That should help clean things up. |
@sosnovsky to address this issue, I will want to only initialize To do that, I'll create a Does that sound reasonable? |
Just noticed, we already pass struct AppContext {
let encryptedStorage: EncryptedStorageType
let session: SessionType?
} |
* issue 1111 only initialize Realm and KeyChainService once * wip * wip * wip * [skip ci] fixed controllers * [skip ci] more fixes * [skip ci] a few more * [skip ci] fix services * mail provider fixes [skip ci] * a few more fixes [skip ci] * a few more fixes [skip ci] * it builds * add to test scope * [skip ci] remove AppReset, fix some test usages * Project file fixed * removed unwanted files from test target * intermediate * fix * PR fixes * PR fixes II * less verbose backup service init * cleanup * controller cleanup * fix * cleanup * issue #1111 fix unit tests running * issue #1131 use in-memory Realm for tests * issue #1111 fix tests Co-authored-by: Ivan <[email protected]> Co-authored-by: Roma Sosnovsky <[email protected]>
I'm still getting this and have to reset storage to continue using the app.
The interesting thing is that it seemed to happen for both apps at the same time this morning. So I wonder, maybe the encryption key we use from keychain is not stable?
The text was updated successfully, but these errors were encountered: