mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-30 05:06:58 +02:00
MESSENGER-2762 Initial Merge
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
//
|
||||
/*
|
||||
* Copyright (c) 2021 BWI GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import MatrixSDK
|
||||
|
||||
|
||||
class SecureFileStorage {
|
||||
|
||||
static let shared: SecureFileStorage = SecureFileStorage(withFilename: "shared")
|
||||
|
||||
private let fileOperationQueue = DispatchQueue(label: "SecureFileStoreQueue")
|
||||
|
||||
private(set) var locked: Bool = true
|
||||
private var fileURL: URL!
|
||||
private let fileName: String
|
||||
private let directoryName: String = "SecureFiles"
|
||||
|
||||
private var crypto: Cryptable!
|
||||
private let saltKeyName: String = "salt"
|
||||
private let iterationsKeyName: String = "iterations"
|
||||
private var memoryVault: MemoryVault!
|
||||
private var dict: [String: Data] = [:]
|
||||
|
||||
private enum SecureStorageError: Error {
|
||||
case locked
|
||||
case noData
|
||||
}
|
||||
|
||||
init(withFilename fileName: String) {
|
||||
self.fileName = fileName
|
||||
setupFilePath()
|
||||
|
||||
let fileContent = loadFromFile()
|
||||
memoryVault = MemoryVault(fileContent)
|
||||
|
||||
let salt = try? memoryVault.string(forKey: saltKeyName)
|
||||
let iterations = try? memoryVault.unsignedInteger(forKey: iterationsKeyName)
|
||||
if iterations == nil && salt == nil {
|
||||
do {
|
||||
try memoryVault.set(makeSalt(), forKey: saltKeyName)
|
||||
dict[saltKeyName] = makeSalt().data(using: .utf8)
|
||||
try memoryVault.set(BwiBuildSettings.iterationsForSecureStorage, forKey: iterationsKeyName)
|
||||
dict[iterationsKeyName] = String(BwiBuildSettings.iterationsForSecureStorage).data(using: .utf8)
|
||||
|
||||
saveToFile()
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func setupFilePath() {
|
||||
var cachePath: URL!
|
||||
|
||||
if let appGroupIdentifier = MXSDKOptions.sharedInstance().applicationGroupIdentifier {
|
||||
cachePath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
|
||||
} else {
|
||||
cachePath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
|
||||
}
|
||||
|
||||
fileURL = cachePath.appendingPathComponent(directoryName).appendingPathComponent(fileName)
|
||||
|
||||
fileOperationQueue.async {
|
||||
try? FileManager.default.createDirectory(at: self.fileURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadFromFile() -> [String: Any] {
|
||||
var fileContent: [String: Any] = [:]
|
||||
fileOperationQueue.sync {
|
||||
if let dict = NSDictionary(contentsOf: self.fileURL) as? [String: Any] {
|
||||
fileContent = dict
|
||||
}
|
||||
}
|
||||
return fileContent
|
||||
}
|
||||
|
||||
private func saveToFile() {
|
||||
fileOperationQueue.async {
|
||||
(self.memoryVault.dict as NSDictionary).write(to: self.fileURL, atomically: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func makeSalt() -> String {
|
||||
let secret = ProcessInfo.processInfo.globallyUniqueString
|
||||
let index = secret.index(secret.startIndex, offsetBy: 32)
|
||||
return String(secret.prefix(upTo: index))
|
||||
}
|
||||
|
||||
func update(passphrase: String) throws {
|
||||
guard !locked else {
|
||||
throw SecureStorageError.locked
|
||||
}
|
||||
guard let salt = try? memoryVault.string(forKey: saltKeyName), let iterations = try? memoryVault.unsignedInteger(forKey: iterationsKeyName) else {
|
||||
throw SecureStorageError.noData
|
||||
}
|
||||
|
||||
let encryptionKey = try MXKeyBackupPassword.retrievePrivateKey(withPassword: passphrase, salt: salt, iterations: iterations)
|
||||
crypto = try CryptoAES(key: encryptionKey)
|
||||
|
||||
try dict.forEach { (key: String, value: Data) in
|
||||
if key == saltKeyName || key == iterationsKeyName {
|
||||
return
|
||||
}
|
||||
let encrypted = try crypto.encrypt(value)
|
||||
try memoryVault.set(encrypted, forKey: key)
|
||||
}
|
||||
|
||||
saveToFile()
|
||||
}
|
||||
|
||||
func unlock(passphrase: String) throws {
|
||||
guard let salt = try? memoryVault.string(forKey: saltKeyName), let iterations = try? memoryVault.unsignedInteger(forKey: iterationsKeyName) else {
|
||||
throw SecureStorageError.noData
|
||||
}
|
||||
|
||||
let encryptionKey = try MXKeyBackupPassword.retrievePrivateKey(withPassword: passphrase, salt: salt, iterations: iterations)
|
||||
crypto = try CryptoAES(key: encryptionKey)
|
||||
try memoryVault.dict.forEach { (key: String, value: Any) in
|
||||
guard let data = value as? Data else {
|
||||
return
|
||||
}
|
||||
if key == saltKeyName || key == iterationsKeyName {
|
||||
dict[key] = data
|
||||
return
|
||||
}
|
||||
dict[key] = try crypto.decrypt(data)
|
||||
}
|
||||
|
||||
locked = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SecureFileStorage: KeyValueVault {
|
||||
|
||||
func objectExists(withKey key: String) -> Bool {
|
||||
return memoryVault.objectExists(withKey: key)
|
||||
}
|
||||
|
||||
func removeObject(forKey key: String) throws {
|
||||
dict.removeValue(forKey: key)
|
||||
try memoryVault.removeObject(forKey: key)
|
||||
|
||||
saveToFile()
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
dict.removeAll()
|
||||
try memoryVault.reset()
|
||||
|
||||
saveToFile()
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
func bool(forKey key: String) throws -> Bool? {
|
||||
if let stringValue = try string(forKey: key) {
|
||||
return Bool(stringValue)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func integer(forKey key: String) throws -> Int? {
|
||||
if let stringValue = try string(forKey: key) {
|
||||
return Int(stringValue)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func unsignedInteger(forKey key: String) throws -> UInt? {
|
||||
if let stringValue = try string(forKey: key) {
|
||||
return UInt(stringValue)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func string(forKey key: String) throws -> String? {
|
||||
if let data = try data(forKey: key) {
|
||||
return String(data: data, encoding: .utf8)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func data(forKey key: String) throws -> Data? {
|
||||
guard !locked else {
|
||||
throw SecureStorageError.locked
|
||||
}
|
||||
return dict[key]
|
||||
}
|
||||
|
||||
// MARK: - Setter
|
||||
|
||||
func set(_ value: Bool?, forKey key: String) throws {
|
||||
if let value = value {
|
||||
try set(String(value).data(using: .utf8), forKey: key)
|
||||
} else {
|
||||
try set(nil as Data?, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
func set(_ value: Int?, forKey key: String) throws {
|
||||
if let value = value {
|
||||
try set(String(value).data(using: .utf8), forKey: key)
|
||||
} else {
|
||||
try set(nil as Data?, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
func set(_ value: UInt?, forKey key: String) throws {
|
||||
if let value = value {
|
||||
try set(String(value).data(using: .utf8), forKey: key)
|
||||
} else {
|
||||
try set(nil as Data?, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
func set(_ value: String?, forKey key: String) throws {
|
||||
try set(value?.data(using: .utf8), forKey: key)
|
||||
}
|
||||
|
||||
func set(_ value: Data?, forKey key: String) throws {
|
||||
guard !locked else {
|
||||
throw SecureStorageError.locked
|
||||
}
|
||||
if let value = value {
|
||||
dict[key] = value
|
||||
let encrypted = try crypto.encrypt(value)
|
||||
try memoryVault.set(encrypted, forKey: key)
|
||||
|
||||
saveToFile()
|
||||
} else {
|
||||
try removeObject(forKey: key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user