You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacy.swift

170 lines
6.7 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable
import Foundation
import YapDatabase
public enum SUKLegacy {
// MARK: - YapDatabase
private static let keychainService = "TSKeyChainService"
private static let keychainDBCipherKeySpec = "OWSDatabaseCipherKeySpec"
private static let sqlCipherKeySpecLength = 48
private static var database: Atomic<YapDatabase>?
// MARK: - Collections and Keys
internal static let userAccountRegisteredNumberKey = "TSStorageRegisteredNumberKey"
internal static let userAccountCollection = "TSStorageUserAccountCollection"
internal static let identityKeyStoreSeedKey = "LKLokiSeed"
internal static let identityKeyStoreEd25519SecretKey = "LKED25519SecretKey"
internal static let identityKeyStoreEd25519PublicKey = "LKED25519PublicKey"
internal static let identityKeyStoreIdentityKey = "TSStorageManagerIdentityKeyStoreIdentityKey"
internal static let identityKeyStoreCollection = "TSStorageManagerIdentityKeyStoreCollection"
// MARK: - Database Functions
public static var legacyDatabaseFilepath: String {
let sharedDirUrl: URL = URL(fileURLWithPath: OWSFileSystem.appSharedDataDirectoryPath())
return sharedDirUrl
.appendingPathComponent("database")
.appendingPathComponent("Signal.sqlite")
.path
}
private static let legacyDatabaseDeserializer: YapDatabaseDeserializer = {
return { (collection: String, key: String, data: Data) -> Any in
/// **Note:** The old `init(forReadingWith:)` method has been deprecated with `init(forReadingFrom:)`
/// and Apple changed the default of `requiresSecureCoding` to be true, this results in some of the types from failing
/// to decode, as a result we need to set it to false here
let unarchiver: NSKeyedUnarchiver? = try? NSKeyedUnarchiver(forReadingFrom: data)
unarchiver?.requiresSecureCoding = false
guard !data.isEmpty, let result = unarchiver?.decodeObject(forKey: "root") else {
return UnknownDBObject()
}
return result
}
}()
public static var hasLegacyDatabaseFile: Bool {
return FileManager.default.fileExists(atPath: legacyDatabaseFilepath)
}
@discardableResult public static func loadDatabaseIfNeeded() -> Bool {
guard SUKLegacy.database == nil else { return true }
/// Ensure the databaseKeySpec exists
var maybeKeyData: Data? = try? SSKDefaultKeychainStorage.shared.data(
forService: keychainService,
key: keychainDBCipherKeySpec
)
defer { if maybeKeyData != nil { maybeKeyData!.resetBytes(in: 0..<maybeKeyData!.count) } }
guard maybeKeyData != nil, maybeKeyData?.count == sqlCipherKeySpecLength else { return false }
// Setup the database options
let options: YapDatabaseOptions = YapDatabaseOptions()
options.corruptAction = .fail
options.enableMultiProcessSupport = true
options.cipherUnencryptedHeaderLength = kSqliteHeaderLength // Needed for iOS to support SQLite writes
options.legacyCipherCompatibilityVersion = 3 // Old DB was SQLCipher V3
options.cipherKeySpecBlock = {
/// To avoid holding the keySpec in memory too long we load it as needed, since we have already confirmed
/// it's existence we can force-try here (the database will crash if it's invalid anyway)
var keySpec: Data = try! SSKDefaultKeychainStorage.shared.data(
forService: keychainService,
key: keychainDBCipherKeySpec
)
defer { keySpec.resetBytes(in: 0..<keySpec.count) }
return keySpec
}
let maybeDatabase: YapDatabase? = YapDatabase(
path: legacyDatabaseFilepath,
serializer: nil,
deserializer: legacyDatabaseDeserializer,
options: options
)
guard let database: YapDatabase = maybeDatabase else { return false }
// Store the database instance atomically
SUKLegacy.database = Atomic(database)
return true
}
public static func newDatabaseConnection() -> YapDatabaseConnection? {
SUKLegacy.loadDatabaseIfNeeded()
return self.database?.wrappedValue.newConnection()
}
public static func clearLegacyDatabaseInstance() {
self.database = nil
}
public static func deleteLegacyDatabaseFilesAndKey() throws {
OWSFileSystem.deleteFile(legacyDatabaseFilepath)
OWSFileSystem.deleteFile("\(legacyDatabaseFilepath)-shm")
OWSFileSystem.deleteFile("\(legacyDatabaseFilepath)-wal")
try SSKDefaultKeychainStorage.shared.remove(service: keychainService, key: keychainDBCipherKeySpec)
}
// MARK: - UnknownDBObject
@objc(LegacyUnknownDBObject)
public class UnknownDBObject: NSObject, NSCoding {
override public init() {}
public required init?(coder: NSCoder) {}
public func encode(with coder: NSCoder) { fatalError("Shouldn't be encoding this type") }
}
// MARK: - LagacyKeyPair
@objc(LegacyKeyPair)
public class KeyPair: NSObject, NSCoding {
private static let keyLength: Int = 32
private static let publicKeyKey: String = "TSECKeyPairPublicKey"
private static let privateKeyKey: String = "TSECKeyPairPrivateKey"
public let publicKey: Data
public let privateKey: Data
public init(
publicKeyData: Data,
privateKeyData: Data
) {
publicKey = publicKeyData
privateKey = privateKeyData
}
public required init?(coder: NSCoder) {
var pubKeyLength: Int = 0
var privKeyLength: Int = 0
guard
let pubKeyBytes: UnsafePointer<UInt8> = coder.decodeBytes(forKey: KeyPair.publicKeyKey, returnedLength: &pubKeyLength),
let privateKeyBytes: UnsafePointer<UInt8> = coder.decodeBytes(forKey: KeyPair.privateKeyKey, returnedLength: &privKeyLength),
pubKeyLength == KeyPair.keyLength,
privKeyLength == KeyPair.keyLength
else {
// Fail if the keys aren't the correct length
return nil
}
publicKey = Data(bytes: pubKeyBytes, count: pubKeyLength)
privateKey = Data(bytes: privateKeyBytes, count: privKeyLength)
}
public func encode(with coder: NSCoder) { fatalError("Shouldn't be encoding this type") }
}
}