From 4aca15f5fc29da94443b062747ec393c58bb1a53 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 00:37:34 +0800 Subject: [PATCH 1/9] WIP --- Chronos.xcodeproj/project.pbxproj | 12 ++- .../xcshareddata/swiftpm/Package.resolved | 78 ------------------- .../App/Onboarding/PasswordSetupView.swift | 38 +++++---- Chronos/App/Tabs/Settings/SettingsTab.swift | 15 +++- Chronos/Chronos.entitlements | 2 +- .../Data/{Vault.swift => ExportVault.swift} | 2 +- Chronos/Database/ChronosCrypto.swift | 13 ++-- Chronos/Database/EncryptedToken.swift | 5 +- Chronos/Database/Vault.swift | 19 +++++ Chronos/Services/CryptoService.swift | 77 +++++++++--------- Chronos/Services/ExportService.swift | 10 +-- Chronos/Services/StateService.swift | 3 + Chronos/Services/SwiftDataService.swift | 2 +- 13 files changed, 127 insertions(+), 149 deletions(-) delete mode 100644 Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename Chronos/Data/{Vault.swift => ExportVault.swift} (71%) create mode 100644 Chronos/Database/Vault.swift diff --git a/Chronos.xcodeproj/project.pbxproj b/Chronos.xcodeproj/project.pbxproj index 55f76ae..e4acaf4 100644 --- a/Chronos.xcodeproj/project.pbxproj +++ b/Chronos.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 6B12B0A02C19DB7800E9ED2D /* ExportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B12B09F2C19DB7800E9ED2D /* ExportService.swift */; }; - 6B12B0A22C19F1E600E9ED2D /* Vault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B12B0A12C19F1E600E9ED2D /* Vault.swift */; }; + 6B12B0A22C19F1E600E9ED2D /* ExportVault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B12B0A12C19F1E600E9ED2D /* ExportVault.swift */; }; 6B19CA722B7A70B800690390 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B19CA712B7A70B800690390 /* WelcomeView.swift */; }; 6B19CA742B7A769900690390 /* PasswordSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B19CA732B7A769900690390 /* PasswordSetupView.swift */; }; 6B2583AF2B96048700938F3A /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2583AE2B96048700938F3A /* SettingsTab.swift */; }; @@ -30,6 +30,7 @@ 6B3BB1202B511A9E00DCEF0B /* SwiftOTP in Frameworks */ = {isa = PBXBuildFile; productRef = 6B3BB11F2B511A9E00DCEF0B /* SwiftOTP */; }; 6B3C7A382BE764E70043FEBD /* StorageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3C7A372BE764E70043FEBD /* StorageSetupView.swift */; }; 6B3C7A3D2BE9E0600043FEBD /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 6B3C7A3C2BE9E0600043FEBD /* Logging */; }; + 6B3F92A92C1B45AA004125A8 /* Vault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3F92A82C1B45AA004125A8 /* Vault.swift */; }; 6B4B48F32BD7BB3C007D357D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4B48F22BD7BB3C007D357D /* Token.swift */; }; 6B5E41CE2BD790F80045DBC6 /* EncryptedToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */; }; 6B66D5E72B526315006DB79D /* AddTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B66D5E62B526315006DB79D /* AddTokenView.swift */; }; @@ -53,7 +54,7 @@ /* Begin PBXFileReference section */ 6B12B09F2C19DB7800E9ED2D /* ExportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportService.swift; sourceTree = ""; }; - 6B12B0A12C19F1E600E9ED2D /* Vault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vault.swift; sourceTree = ""; }; + 6B12B0A12C19F1E600E9ED2D /* ExportVault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportVault.swift; sourceTree = ""; }; 6B19CA712B7A70B800690390 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 6B19CA732B7A769900690390 /* PasswordSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordSetupView.swift; sourceTree = ""; }; 6B2583AE2B96048700938F3A /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; @@ -73,6 +74,7 @@ 6B3BB0DF2B4ECE6F00DCEF0B /* TOTPRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPRowView.swift; sourceTree = ""; }; 6B3BB0E12B4ED19300DCEF0B /* TokensTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensTab.swift; sourceTree = ""; }; 6B3C7A372BE764E70043FEBD /* StorageSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSetupView.swift; sourceTree = ""; }; + 6B3F92A82C1B45AA004125A8 /* Vault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vault.swift; sourceTree = ""; }; 6B4B48F22BD7BB3C007D357D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedToken.swift; sourceTree = ""; }; 6B66D5E62B526315006DB79D /* AddTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTokenView.swift; sourceTree = ""; }; @@ -219,6 +221,7 @@ children = ( 6BE122912BD6413D008636D2 /* ChronosCrypto.swift */, 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */, + 6B3F92A82C1B45AA004125A8 /* Vault.swift */, ); path = Database; sourceTree = ""; @@ -258,7 +261,7 @@ isa = PBXGroup; children = ( 6B4B48F22BD7BB3C007D357D /* Token.swift */, - 6B12B0A12C19F1E600E9ED2D /* Vault.swift */, + 6B12B0A12C19F1E600E9ED2D /* ExportVault.swift */, ); path = Data; sourceTree = ""; @@ -410,7 +413,7 @@ files = ( 6B3BB0B32B4EA87C00DCEF0B /* ContentView.swift in Sources */, 6B3BB0B12B4EA87C00DCEF0B /* ChronosApp.swift in Sources */, - 6B12B0A22C19F1E600E9ED2D /* Vault.swift in Sources */, + 6B12B0A22C19F1E600E9ED2D /* ExportVault.swift in Sources */, 6B7383E52B9C4230008E8867 /* Container.swift in Sources */, 6B3C7A382BE764E70043FEBD /* StorageSetupView.swift in Sources */, 6BC3C3B52BA6B91E00B181B9 /* BiometricsSetupView.swift in Sources */, @@ -420,6 +423,7 @@ 6B66D5E72B526315006DB79D /* AddTokenView.swift in Sources */, 6B2583AF2B96048700938F3A /* SettingsTab.swift in Sources */, 6B39629C2BF5EB3E000410B0 /* AuthenticationView.swift in Sources */, + 6B3F92A92C1B45AA004125A8 /* Vault.swift in Sources */, 6B9581E22B62AFF80029EF3C /* ViewModifier.swift in Sources */, 6BE122922BD6413D008636D2 /* ChronosCrypto.swift in Sources */, 6B19CA742B7A769900690390 /* PasswordSetupView.swift in Sources */, diff --git a/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 3547a74..0000000 --- a/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,78 +0,0 @@ -{ - "originHash" : "fce3d4a72e08c7c2acd350debe307850f30913bd1e53ad0114fb221b3f8ffe73", - "pins" : [ - { - "identity" : "alertkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparrowcode/AlertKit", - "state" : { - "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", - "version" : "5.1.9" - } - }, - { - "identity" : "codescanner", - "kind" : "remoteSourceControl", - "location" : "https://github.com/twostraws/CodeScanner.git", - "state" : { - "revision" : "34da57fb63b47add20de8a85da58191523ccce57", - "version" : "2.5.0" - } - }, - { - "identity" : "cryptoswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", - "state" : { - "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", - "version" : "1.8.2" - } - }, - { - "identity" : "factory", - "kind" : "remoteSourceControl", - "location" : "https://github.com/hmlongco/Factory", - "state" : { - "revision" : "587995f7d5cc667951d635fbf6b4252324ba0439", - "version" : "2.3.2" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", - "version" : "3.4.0" - } - }, - { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", - "version" : "1.5.4" - } - }, - { - "identity" : "swiftotp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/lachlanbell/SwiftOTP.git", - "state" : { - "revision" : "9660551ea3df153c3cbacfa34ac3abbec73a8b84", - "version" : "3.0.2" - } - }, - { - "identity" : "valet", - "kind" : "remoteSourceControl", - "location" : "https://github.com/square/Valet", - "state" : { - "revision" : "3df8eaa90e1fa0d80830733cdc3c9e098146af3d", - "version" : "4.3.0" - } - } - ], - "version" : 3 -} diff --git a/Chronos/App/Onboarding/PasswordSetupView.swift b/Chronos/App/Onboarding/PasswordSetupView.swift index 5a3adc5..f1538e9 100644 --- a/Chronos/App/Onboarding/PasswordSetupView.swift +++ b/Chronos/App/Onboarding/PasswordSetupView.swift @@ -1,35 +1,42 @@ import Factory import SwiftUI +import SwiftData enum FocusedField { case password, verifyPassword } struct PasswordSetupView: View { + @Environment(\.modelContext) private var modelContext + @State private var password: String = "" @State private var verifyPassword: String = "" @State private var nextBtnPressed: Bool = false @State private var isEncrypting: Bool = false - + + @Query() private var vaults: [Vault] + @FocusState private var focusedField: FocusedField? + + @AppStorage(StateEnum.VAULT.rawValue) var stateVaultId: String? let cryptoService = Container.shared.cryptoService() let stateService = Container.shared.stateService() - + var body: some View { ScrollView { VStack { Image(systemName: "ellipsis.rectangle") .font(.system(size: 44)) .padding(.bottom, 16) - + Text("Your master password is used to encrypt your data securely. Choose a memorable, random, and unique password with at least 10 characters.") .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.center) - + Text("Your master password") .padding(.top, 32) - + Group { SecureField("", text: $password) .multilineTextAlignment(.center) @@ -39,7 +46,7 @@ struct PasswordSetupView: View { .frame(height: 48) .background(Color(.systemGray6)) .cornerRadius(8) - + Text("Confirm your master password") .padding(.top, 24) Group { @@ -51,12 +58,12 @@ struct PasswordSetupView: View { .frame(height: 48) .background(Color(.systemGray6)) .cornerRadius(8) - + Spacer() - + Button { isEncrypting = true - + Task { await generateAndEncryptMasterKey() } @@ -96,7 +103,12 @@ struct PasswordSetupView: View { extension PasswordSetupView { func generateAndEncryptMasterKey() async { stateService.masterKey = try! cryptoService.generateRandomMasterKey() - + + let vault = Vault(vaultId: UUID(), chronosCryptos: [], encryptedTokens: []) + modelContext.insert(vault) + + stateService.vault = vault + await cryptoService.wrapMasterKeyWithUserPassword(password: Array(password.utf8)) nextBtnPressed = true } @@ -105,15 +117,15 @@ extension PasswordSetupView { extension PasswordSetupView { var isPasswordValid: Bool { var valid = true - + if password != verifyPassword { valid = false } - + if password.count < 10 { valid = false } - + return valid } } diff --git a/Chronos/App/Tabs/Settings/SettingsTab.swift b/Chronos/App/Tabs/Settings/SettingsTab.swift index 823666b..415c842 100644 --- a/Chronos/App/Tabs/Settings/SettingsTab.swift +++ b/Chronos/App/Tabs/Settings/SettingsTab.swift @@ -1,14 +1,16 @@ import Factory import LinkPresentation import SwiftUI +import SwiftData struct SettingsTab: View { @EnvironmentObject private var loginStatus: LoginStatus @Environment(\.scenePhase) private var scenePhase + @Environment(\.modelContext) private var modelContext @AppStorage(StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) private var stateBiometricsAuth: Bool = false @AppStorage(StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) private var isICloudEnabled: Bool = false - + private let secureEnclaveService = Container.shared.secureEnclaveService() private let swiftDataService = Container.shared.swiftDataService() private let stateService = Container.shared.stateService() @@ -107,6 +109,17 @@ struct SettingsTab: View { } } .listSectionSpacing(8) + + Section { + Button { + modelContext.delete(stateService.vault!) + } label: { + Text("Delete Vault") + .foregroundStyle(.red) + .frame(maxWidth: .infinity) + } + } + .listSectionSpacing(8) } .scrollContentBackground(.hidden) .background(Color(red: 0.04, green: 0, blue: 0.11)) diff --git a/Chronos/Chronos.entitlements b/Chronos/Chronos.entitlements index 3eda263..e898392 100644 --- a/Chronos/Chronos.entitlements +++ b/Chronos/Chronos.entitlements @@ -6,7 +6,7 @@ development com.apple.developer.icloud-container-identifiers - iCloud.com.joeldavidw.chronos.test + iCloud.com.joeldavidw.ChronosApp com.apple.developer.icloud-services diff --git a/Chronos/Data/Vault.swift b/Chronos/Data/ExportVault.swift similarity index 71% rename from Chronos/Data/Vault.swift rename to Chronos/Data/ExportVault.swift index 5e1482a..5ab84c6 100644 --- a/Chronos/Data/Vault.swift +++ b/Chronos/Data/ExportVault.swift @@ -1,6 +1,6 @@ import Foundation -class Vault: Codable { +class ExportVault: Codable { var tokens: [Token]? var errors: [String]? } diff --git a/Chronos/Database/ChronosCrypto.swift b/Chronos/Database/ChronosCrypto.swift index 7fc8263..c80321c 100644 --- a/Chronos/Database/ChronosCrypto.swift +++ b/Chronos/Database/ChronosCrypto.swift @@ -9,14 +9,12 @@ struct PasswordParams: Codable { var salt: String } -enum KdfEnum: String, Codable, CaseIterable, Identifiable { - var id: String { rawValue } - case SCRYPT -} +// Type: +// 0: Scrypt struct KdfParams: Codable { - var type: KdfEnum + var type: Int var n: Int var r: Int var p: Int @@ -24,12 +22,15 @@ struct KdfParams: Codable { @Model class ChronosCrypto { + var vault: Vault? + var key: [UInt8]? var keyParams: KeyParams? var passwordParams: PasswordParams? var kdfParams: KdfParams? - init(key: [UInt8], keyParams: KeyParams, passwordParams: PasswordParams, kdfParams: KdfParams) { + init(vault: Vault, key: [UInt8]? = nil, keyParams: KeyParams? = nil, passwordParams: PasswordParams? = nil, kdfParams: KdfParams? = nil) { + self.vault = vault self.key = key self.keyParams = keyParams self.passwordParams = passwordParams diff --git a/Chronos/Database/EncryptedToken.swift b/Chronos/Database/EncryptedToken.swift index 3ac4579..324641f 100644 --- a/Chronos/Database/EncryptedToken.swift +++ b/Chronos/Database/EncryptedToken.swift @@ -3,12 +3,15 @@ import SwiftData @Model class EncryptedToken { + var vault: Vault? + var encryptedTokenCiper: [UInt8]? var iv: [UInt8]? var authenticationTag: [UInt8]? var createdAt: Date? - init(encryptedTokenCiper: [UInt8], iv: [UInt8], authenticationTag: [UInt8], createdAt: Date) { + init(vault: Vault, encryptedTokenCiper: [UInt8]? = nil, iv: [UInt8]? = nil, authenticationTag: [UInt8]? = nil, createdAt: Date? = nil) { + self.vault = vault self.encryptedTokenCiper = encryptedTokenCiper self.iv = iv self.authenticationTag = authenticationTag diff --git a/Chronos/Database/Vault.swift b/Chronos/Database/Vault.swift new file mode 100644 index 0000000..7f4749c --- /dev/null +++ b/Chronos/Database/Vault.swift @@ -0,0 +1,19 @@ +import Foundation +import SwiftData + +@Model +class Vault { + var vaultId: UUID? + + @Relationship(deleteRule: .cascade, inverse: \ChronosCrypto.vault) + var chronosCryptos: [ChronosCrypto]? + + @Relationship(deleteRule: .cascade, inverse: \EncryptedToken.vault) + var encryptedTokens: [EncryptedToken]? + + init(vaultId: UUID? = nil, chronosCryptos: [ChronosCrypto]? = nil, encryptedTokens: [EncryptedToken]? = nil) { + self.vaultId = vaultId + self.chronosCryptos = chronosCryptos + self.encryptedTokens = encryptedTokens + } +} diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index 8d29972..28a3630 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -11,58 +11,59 @@ enum CryptoError: Error { public class CryptoService { let stateService = Container.shared.stateService() let swiftDataService = Container.shared.swiftDataService() - + let defaults = UserDefaults.standard - + // scrypt paramaters - n: 2^17, r: 8, p: 1 - let kdfParams = KdfParams(type: KdfEnum.SCRYPT, n: 1 << 17, r: 8, p: 1) - + let kdfParams = KdfParams(type: 0, n: 1 << 17, r: 8, p: 1) + func wrapMasterKeyWithUserPassword(password: [UInt8]) async { let passwordSalt = try! generateRandomSaltHexString() let passwordParams = PasswordParams(salt: passwordSalt) - + let passwordHash = Array(createPasswordHash(password: password, salt: passwordSalt, kdfParms: kdfParams)!) - + let iv = Array(try! generateRandom192BitNonce()) let header = Array("".utf8) - + do { let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(stateService.masterKey), key: passwordHash, iv: iv, authenticationHeader: header) - + let keyParams = KeyParams(iv: iv, tag: encrypt.authenticationTag) - - let newPasswordCrypto = ChronosCrypto(key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) - + + let newPasswordCrypto = ChronosCrypto(vault: stateService.vault!, key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) + let context = ModelContext(swiftDataService.getModelContainer()) + context.insert(newPasswordCrypto) try context.save() } catch { fatalError(error.localizedDescription) } } - + func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore: Bool = false) async -> Bool { let context = ModelContext(swiftDataService.getModelContainer(isRestore: isRestore)) - + let cryptoArr = try! context.fetch(FetchDescriptor()) - + if cryptoArr.isEmpty { stateService.resetAllStates() } - + let crypto: ChronosCrypto = cryptoArr.first! - + let passwordHash = Array(createPasswordHash(password: password, salt: crypto.passwordParams!.salt, kdfParms: kdfParams)!) - + let header = Array("".utf8) - + do { guard let cryptoKey = crypto.key, let keyParams = crypto.keyParams else { fatalError("var nil") } - + var decrypt = try AEADXChaCha20Poly1305.decrypt(cryptoKey, key: passwordHash, iv: keyParams.iv, authenticationHeader: header, authenticationTag: keyParams.tag) - + if decrypt.success { stateService.masterKey = SecureBytes(bytes: decrypt.plainText) decrypt.plainText.removeAll() @@ -81,24 +82,24 @@ extension CryptoService { do { let iv = try Array(generateRandom192BitNonce()) let header = Array("".utf8) - + let tokenJson = try JSONEncoder().encode(token) let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(tokenJson), key: Array(stateService.masterKey), iv: iv, authenticationHeader: header) - - return EncryptedToken(encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) + + return EncryptedToken(vault: stateService.vault!, encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) } catch { fatalError(error.localizedDescription) } } - + func updateEncryptedToken(encryptedToken: EncryptedToken, token: Token) { do { let iv = try Array(generateRandom192BitNonce()) let header = Array("".utf8) - + let tokenJson = try JSONEncoder().encode(token) let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(tokenJson), key: Array(stateService.masterKey), iv: iv, authenticationHeader: header) - + encryptedToken.encryptedTokenCiper = encrypt.cipherText encryptedToken.authenticationTag = encrypt.authenticationTag encryptedToken.iv = iv @@ -107,17 +108,17 @@ extension CryptoService { fatalError(error.localizedDescription) } } - + func decryptToken(encryptedToken: EncryptedToken) -> Token? { let header = Array("".utf8) - + guard let encryptedTokenCiper = encryptedToken.encryptedTokenCiper, let iv = encryptedToken.iv, let authenticationTag = encryptedToken.authenticationTag else { fatalError("var nil") } - + do { let decrypted = try AEADXChaCha20Poly1305.decrypt(encryptedTokenCiper, key: Array(stateService.masterKey), iv: iv, authenticationHeader: header, authenticationTag: authenticationTag) - + let tokenJson = try JSONDecoder().decode(Token.self, from: Data(decrypted.plainText)) return tokenJson } catch { @@ -129,7 +130,7 @@ extension CryptoService { extension CryptoService { func createPasswordHash(password: [UInt8], salt: String, kdfParms: KdfParams) -> Data? { let password = Array(password) - + do { let hashedSecret = try Scrypt(password: password, salt: Array(hex: salt), dkLen: 32, N: kdfParms.n, r: kdfParms.r, p: kdfParms.p).calculate() return Data(hashedSecret) @@ -137,33 +138,33 @@ extension CryptoService { return nil } } - + // Generates a 256 bit master key func generateRandomMasterKey() throws -> SecureBytes { return try SecureBytes(bytes: Array(generateRandomBytes(count: 32))) } - + private func generateRandomBiometricsPassword() throws -> SecureBytes { return try SecureBytes(bytes: Array(generateRandomBytes(count: 32))) } - + private func generateRandomSaltHexString() throws -> String { let bytes = try generateRandomBytes(count: 32) return bytes.toHexString() } - + private func generateRandom192BitNonce() throws -> Data { return try generateRandomBytes(count: 24) } - + private func generateRandomBytes(count: Int) throws -> Data { var bytes = [UInt8](repeating: 0, count: count) let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) - + guard result == errSecSuccess else { throw CryptoError.randomBytesGenerationFailed } - + return Data(bytes) } } diff --git a/Chronos/Services/ExportService.swift b/Chronos/Services/ExportService.swift index 9b24e1a..804b88e 100644 --- a/Chronos/Services/ExportService.swift +++ b/Chronos/Services/ExportService.swift @@ -18,7 +18,7 @@ public class ExportService { let context = ModelContext(swiftDataService.getModelContainer()) let encryptedTokenArr = try! context.fetch(FetchDescriptor()) - let vault = Vault() + let exportVault = ExportVault() var tokens: [Token] = [] var errors: [String] = [] @@ -39,15 +39,15 @@ public class ExportService { errors.append("\(numOfTokenFailedToDecode) out of \(encryptedTokenArr.count) tokens failed to be export") } - vault.tokens = tokens - vault.errors = errors + exportVault.tokens = tokens + exportVault.errors = errors let url = FileManager.default.temporaryDirectory .appendingPathComponent("Chronos_" + Date().formatted(verbatimStyle)) .appendingPathExtension("json") - guard let jsonData = try? JSONEncoder().encode(vault) else { - logger.error("Unable to encode vault") + guard let jsonData = try? JSONEncoder().encode(exportVault) else { + logger.error("Unable to encode exportVault") return nil } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index b9458fe..ba77d76 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -2,6 +2,8 @@ import Factory import Foundation enum StateEnum: String { + case VAULT + case ICLOUD_BACKUP_ENABLED case BIOMETRICS_AUTH_ENABLED case ONBOARDING_COMPLETED @@ -13,6 +15,7 @@ public class StateService { private let defaults = UserDefaults.standard var masterKey: SecureBytes = .init(bytes: []) + var vault: Vault? func resetAllStates() { defaults.setValue(false, forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) diff --git a/Chronos/Services/SwiftDataService.swift b/Chronos/Services/SwiftDataService.swift index 510a884..50349ad 100644 --- a/Chronos/Services/SwiftDataService.swift +++ b/Chronos/Services/SwiftDataService.swift @@ -11,7 +11,7 @@ public class SwiftDataService { private lazy var cloudModelContainer: ModelContainer = setupModelContainer(storeName: "onlineChronos.sqlite", cloudKitDatabase: .automatic) - let schema = Schema([ChronosCrypto.self, EncryptedToken.self]) + let schema = Schema([Vault.self, ChronosCrypto.self, EncryptedToken.self]) init() { _ = localModelContainer From 6959f712ac01531530c9d6caa2b76a3a5776a8d9 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 20:58:11 +0800 Subject: [PATCH 2/9] Updated restore --- .../xcshareddata/swiftpm/Package.resolved | 78 ++++++++++++ .../App/Onboarding/PasswordSetupView.swift | 40 +++--- Chronos/App/Tabs/Settings/SettingsTab.swift | 6 +- Chronos/Database/ChronosCrypto.swift | 3 +- Chronos/Database/EncryptedToken.swift | 2 +- Chronos/Database/Vault.swift | 10 +- Chronos/Services/CryptoService.swift | 114 +++++++++++------- Chronos/Services/StateService.swift | 2 +- 8 files changed, 179 insertions(+), 76 deletions(-) create mode 100644 Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..3547a74 --- /dev/null +++ b/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,78 @@ +{ + "originHash" : "fce3d4a72e08c7c2acd350debe307850f30913bd1e53ad0114fb221b3f8ffe73", + "pins" : [ + { + "identity" : "alertkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparrowcode/AlertKit", + "state" : { + "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", + "version" : "5.1.9" + } + }, + { + "identity" : "codescanner", + "kind" : "remoteSourceControl", + "location" : "https://github.com/twostraws/CodeScanner.git", + "state" : { + "revision" : "34da57fb63b47add20de8a85da58191523ccce57", + "version" : "2.5.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" + } + }, + { + "identity" : "factory", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hmlongco/Factory", + "state" : { + "revision" : "587995f7d5cc667951d635fbf6b4252324ba0439", + "version" : "2.3.2" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", + "version" : "3.4.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" + } + }, + { + "identity" : "swiftotp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lachlanbell/SwiftOTP.git", + "state" : { + "revision" : "9660551ea3df153c3cbacfa34ac3abbec73a8b84", + "version" : "3.0.2" + } + }, + { + "identity" : "valet", + "kind" : "remoteSourceControl", + "location" : "https://github.com/square/Valet", + "state" : { + "revision" : "3df8eaa90e1fa0d80830733cdc3c9e098146af3d", + "version" : "4.3.0" + } + } + ], + "version" : 3 +} diff --git a/Chronos/App/Onboarding/PasswordSetupView.swift b/Chronos/App/Onboarding/PasswordSetupView.swift index f1538e9..bd7aa6a 100644 --- a/Chronos/App/Onboarding/PasswordSetupView.swift +++ b/Chronos/App/Onboarding/PasswordSetupView.swift @@ -1,6 +1,6 @@ import Factory -import SwiftUI import SwiftData +import SwiftUI enum FocusedField { case password, verifyPassword @@ -8,35 +8,35 @@ enum FocusedField { struct PasswordSetupView: View { @Environment(\.modelContext) private var modelContext - + @State private var password: String = "" @State private var verifyPassword: String = "" @State private var nextBtnPressed: Bool = false @State private var isEncrypting: Bool = false - + @Query() private var vaults: [Vault] - + @FocusState private var focusedField: FocusedField? - + @AppStorage(StateEnum.VAULT.rawValue) var stateVaultId: String? let cryptoService = Container.shared.cryptoService() let stateService = Container.shared.stateService() - + var body: some View { ScrollView { VStack { Image(systemName: "ellipsis.rectangle") .font(.system(size: 44)) .padding(.bottom, 16) - + Text("Your master password is used to encrypt your data securely. Choose a memorable, random, and unique password with at least 10 characters.") .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.center) - + Text("Your master password") .padding(.top, 32) - + Group { SecureField("", text: $password) .multilineTextAlignment(.center) @@ -46,7 +46,7 @@ struct PasswordSetupView: View { .frame(height: 48) .background(Color(.systemGray6)) .cornerRadius(8) - + Text("Confirm your master password") .padding(.top, 24) Group { @@ -58,12 +58,12 @@ struct PasswordSetupView: View { .frame(height: 48) .background(Color(.systemGray6)) .cornerRadius(8) - + Spacer() - + Button { isEncrypting = true - + Task { await generateAndEncryptMasterKey() } @@ -103,12 +103,12 @@ struct PasswordSetupView: View { extension PasswordSetupView { func generateAndEncryptMasterKey() async { stateService.masterKey = try! cryptoService.generateRandomMasterKey() - - let vault = Vault(vaultId: UUID(), chronosCryptos: [], encryptedTokens: []) + + let vault = Vault(vaultId: UUID(), createdAt: Date(), chronosCryptos: [], encryptedTokens: []) modelContext.insert(vault) - + stateService.vault = vault - + await cryptoService.wrapMasterKeyWithUserPassword(password: Array(password.utf8)) nextBtnPressed = true } @@ -117,15 +117,15 @@ extension PasswordSetupView { extension PasswordSetupView { var isPasswordValid: Bool { var valid = true - + if password != verifyPassword { valid = false } - + if password.count < 10 { valid = false } - + return valid } } diff --git a/Chronos/App/Tabs/Settings/SettingsTab.swift b/Chronos/App/Tabs/Settings/SettingsTab.swift index 415c842..31d5f75 100644 --- a/Chronos/App/Tabs/Settings/SettingsTab.swift +++ b/Chronos/App/Tabs/Settings/SettingsTab.swift @@ -1,7 +1,7 @@ import Factory import LinkPresentation -import SwiftUI import SwiftData +import SwiftUI struct SettingsTab: View { @EnvironmentObject private var loginStatus: LoginStatus @@ -10,7 +10,7 @@ struct SettingsTab: View { @AppStorage(StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) private var stateBiometricsAuth: Bool = false @AppStorage(StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) private var isICloudEnabled: Bool = false - + private let secureEnclaveService = Container.shared.secureEnclaveService() private let swiftDataService = Container.shared.swiftDataService() private let stateService = Container.shared.stateService() @@ -109,7 +109,7 @@ struct SettingsTab: View { } } .listSectionSpacing(8) - + Section { Button { modelContext.delete(stateService.vault!) diff --git a/Chronos/Database/ChronosCrypto.swift b/Chronos/Database/ChronosCrypto.swift index c80321c..e0a4af3 100644 --- a/Chronos/Database/ChronosCrypto.swift +++ b/Chronos/Database/ChronosCrypto.swift @@ -9,7 +9,6 @@ struct PasswordParams: Codable { var salt: String } - // Type: // 0: Scrypt @@ -23,7 +22,7 @@ struct KdfParams: Codable { @Model class ChronosCrypto { var vault: Vault? - + var key: [UInt8]? var keyParams: KeyParams? var passwordParams: PasswordParams? diff --git a/Chronos/Database/EncryptedToken.swift b/Chronos/Database/EncryptedToken.swift index 324641f..9e75789 100644 --- a/Chronos/Database/EncryptedToken.swift +++ b/Chronos/Database/EncryptedToken.swift @@ -4,7 +4,7 @@ import SwiftData @Model class EncryptedToken { var vault: Vault? - + var encryptedTokenCiper: [UInt8]? var iv: [UInt8]? var authenticationTag: [UInt8]? diff --git a/Chronos/Database/Vault.swift b/Chronos/Database/Vault.swift index 7f4749c..c6146fa 100644 --- a/Chronos/Database/Vault.swift +++ b/Chronos/Database/Vault.swift @@ -4,15 +4,17 @@ import SwiftData @Model class Vault { var vaultId: UUID? - + var createdAt: Date? + @Relationship(deleteRule: .cascade, inverse: \ChronosCrypto.vault) var chronosCryptos: [ChronosCrypto]? - + @Relationship(deleteRule: .cascade, inverse: \EncryptedToken.vault) var encryptedTokens: [EncryptedToken]? - - init(vaultId: UUID? = nil, chronosCryptos: [ChronosCrypto]? = nil, encryptedTokens: [EncryptedToken]? = nil) { + + init(vaultId: UUID? = nil, createdAt: Date? = nil, chronosCryptos: [ChronosCrypto]? = nil, encryptedTokens: [EncryptedToken]? = nil) { self.vaultId = vaultId + self.createdAt = createdAt self.chronosCryptos = chronosCryptos self.encryptedTokens = encryptedTokens } diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index 28a3630..7f3482f 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -1,6 +1,7 @@ import CryptoSwift import Factory import Foundation +import Logging import SwiftData enum CryptoError: Error { @@ -9,70 +10,93 @@ enum CryptoError: Error { } public class CryptoService { - let stateService = Container.shared.stateService() - let swiftDataService = Container.shared.swiftDataService() - - let defaults = UserDefaults.standard - + private let logger = Logger(label: "CryptoService") + + private let stateService = Container.shared.stateService() + private let swiftDataService = Container.shared.swiftDataService() + + private let defaults = UserDefaults.standard + // scrypt paramaters - n: 2^17, r: 8, p: 1 - let kdfParams = KdfParams(type: 0, n: 1 << 17, r: 8, p: 1) - + private let kdfParams = KdfParams(type: 0, n: 1 << 17, r: 8, p: 1) + func wrapMasterKeyWithUserPassword(password: [UInt8]) async { let passwordSalt = try! generateRandomSaltHexString() let passwordParams = PasswordParams(salt: passwordSalt) - + let passwordHash = Array(createPasswordHash(password: password, salt: passwordSalt, kdfParms: kdfParams)!) - + let iv = Array(try! generateRandom192BitNonce()) let header = Array("".utf8) - + do { let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(stateService.masterKey), key: passwordHash, iv: iv, authenticationHeader: header) - + let keyParams = KeyParams(iv: iv, tag: encrypt.authenticationTag) - + let newPasswordCrypto = ChronosCrypto(vault: stateService.vault!, key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) - + let context = ModelContext(swiftDataService.getModelContainer()) - + context.insert(newPasswordCrypto) try context.save() } catch { fatalError(error.localizedDescription) } } - + func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore: Bool = false) async -> Bool { let context = ModelContext(swiftDataService.getModelContainer(isRestore: isRestore)) - - let cryptoArr = try! context.fetch(FetchDescriptor()) - + + // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. + guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { + logger.error("No vaults found") + return false + } + + guard let vault = vaultArr.first else { + logger.error("Empty vaultArr") + return false + } + + guard let cryptoArr = vault.chronosCryptos else { + logger.error("No crypto found in vault") + return false + } + if cryptoArr.isEmpty { - stateService.resetAllStates() + logger.error("Empty crypto found in vault") + return false + } + + guard let crypto = cryptoArr.first else { + logger.error("Empty crypto found in vault") + return false } - - let crypto: ChronosCrypto = cryptoArr.first! - + let passwordHash = Array(createPasswordHash(password: password, salt: crypto.passwordParams!.salt, kdfParms: kdfParams)!) - + let header = Array("".utf8) - + do { guard let cryptoKey = crypto.key, let keyParams = crypto.keyParams else { - fatalError("var nil") + logger.error("cryptoKey or keyParams is nil") + return false } - + var decrypt = try AEADXChaCha20Poly1305.decrypt(cryptoKey, key: passwordHash, iv: keyParams.iv, authenticationHeader: header, authenticationTag: keyParams.tag) - + if decrypt.success { stateService.masterKey = SecureBytes(bytes: decrypt.plainText) decrypt.plainText.removeAll() return true } else { + logger.error("Unable to decrypt crypto with passwordHash") return false } } catch { - fatalError(error.localizedDescription) + logger.error("Error encountered while decrypting crypto. Error: \(error.localizedDescription)") + return false } } } @@ -82,24 +106,24 @@ extension CryptoService { do { let iv = try Array(generateRandom192BitNonce()) let header = Array("".utf8) - + let tokenJson = try JSONEncoder().encode(token) let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(tokenJson), key: Array(stateService.masterKey), iv: iv, authenticationHeader: header) - + return EncryptedToken(vault: stateService.vault!, encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) } catch { fatalError(error.localizedDescription) } } - + func updateEncryptedToken(encryptedToken: EncryptedToken, token: Token) { do { let iv = try Array(generateRandom192BitNonce()) let header = Array("".utf8) - + let tokenJson = try JSONEncoder().encode(token) let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(tokenJson), key: Array(stateService.masterKey), iv: iv, authenticationHeader: header) - + encryptedToken.encryptedTokenCiper = encrypt.cipherText encryptedToken.authenticationTag = encrypt.authenticationTag encryptedToken.iv = iv @@ -108,17 +132,17 @@ extension CryptoService { fatalError(error.localizedDescription) } } - + func decryptToken(encryptedToken: EncryptedToken) -> Token? { let header = Array("".utf8) - + guard let encryptedTokenCiper = encryptedToken.encryptedTokenCiper, let iv = encryptedToken.iv, let authenticationTag = encryptedToken.authenticationTag else { fatalError("var nil") } - + do { let decrypted = try AEADXChaCha20Poly1305.decrypt(encryptedTokenCiper, key: Array(stateService.masterKey), iv: iv, authenticationHeader: header, authenticationTag: authenticationTag) - + let tokenJson = try JSONDecoder().decode(Token.self, from: Data(decrypted.plainText)) return tokenJson } catch { @@ -130,7 +154,7 @@ extension CryptoService { extension CryptoService { func createPasswordHash(password: [UInt8], salt: String, kdfParms: KdfParams) -> Data? { let password = Array(password) - + do { let hashedSecret = try Scrypt(password: password, salt: Array(hex: salt), dkLen: 32, N: kdfParms.n, r: kdfParms.r, p: kdfParms.p).calculate() return Data(hashedSecret) @@ -138,33 +162,33 @@ extension CryptoService { return nil } } - + // Generates a 256 bit master key func generateRandomMasterKey() throws -> SecureBytes { return try SecureBytes(bytes: Array(generateRandomBytes(count: 32))) } - + private func generateRandomBiometricsPassword() throws -> SecureBytes { return try SecureBytes(bytes: Array(generateRandomBytes(count: 32))) } - + private func generateRandomSaltHexString() throws -> String { let bytes = try generateRandomBytes(count: 32) return bytes.toHexString() } - + private func generateRandom192BitNonce() throws -> Data { return try generateRandomBytes(count: 24) } - + private func generateRandomBytes(count: Int) throws -> Data { var bytes = [UInt8](repeating: 0, count: count) let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) - + guard result == errSecSuccess else { throw CryptoError.randomBytesGenerationFailed } - + return Data(bytes) } } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index ba77d76..84d1534 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -3,7 +3,7 @@ import Foundation enum StateEnum: String { case VAULT - + case ICLOUD_BACKUP_ENABLED case BIOMETRICS_AUTH_ENABLED case ONBOARDING_COMPLETED From 89f013fc695525ec470f5fc8e0cc8afdd2ba4583 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 21:02:06 +0800 Subject: [PATCH 3/9] cleanup imports --- Chronos/App/Tabs/Settings/SettingsTab.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Chronos/App/Tabs/Settings/SettingsTab.swift b/Chronos/App/Tabs/Settings/SettingsTab.swift index 31d5f75..4ee7689 100644 --- a/Chronos/App/Tabs/Settings/SettingsTab.swift +++ b/Chronos/App/Tabs/Settings/SettingsTab.swift @@ -1,12 +1,9 @@ import Factory -import LinkPresentation -import SwiftData import SwiftUI struct SettingsTab: View { @EnvironmentObject private var loginStatus: LoginStatus @Environment(\.scenePhase) private var scenePhase - @Environment(\.modelContext) private var modelContext @AppStorage(StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) private var stateBiometricsAuth: Bool = false @AppStorage(StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) private var isICloudEnabled: Bool = false @@ -109,17 +106,6 @@ struct SettingsTab: View { } } .listSectionSpacing(8) - - Section { - Button { - modelContext.delete(stateService.vault!) - } label: { - Text("Delete Vault") - .foregroundStyle(.red) - .frame(maxWidth: .infinity) - } - } - .listSectionSpacing(8) } .scrollContentBackground(.hidden) .background(Color(red: 0.04, green: 0, blue: 0.11)) From 772b1849e3a451b8bfecefbedf9cdfbcad123676 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 22:21:38 +0800 Subject: [PATCH 4/9] Added vaultService --- Chronos.xcodeproj/project.pbxproj | 4 ++ .../App/Onboarding/PasswordSetupView.swift | 2 - Chronos/Services/Container.swift | 4 ++ Chronos/Services/CryptoService.swift | 20 +++----- Chronos/Services/StateService.swift | 17 ++++++- Chronos/Services/VaultService.swift | 48 +++++++++++++++++++ 6 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 Chronos/Services/VaultService.swift diff --git a/Chronos.xcodeproj/project.pbxproj b/Chronos.xcodeproj/project.pbxproj index e4acaf4..08b3532 100644 --- a/Chronos.xcodeproj/project.pbxproj +++ b/Chronos.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 6B3C7A382BE764E70043FEBD /* StorageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3C7A372BE764E70043FEBD /* StorageSetupView.swift */; }; 6B3C7A3D2BE9E0600043FEBD /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 6B3C7A3C2BE9E0600043FEBD /* Logging */; }; 6B3F92A92C1B45AA004125A8 /* Vault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3F92A82C1B45AA004125A8 /* Vault.swift */; }; + 6B3F92AB2C1C7987004125A8 /* VaultService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3F92AA2C1C7987004125A8 /* VaultService.swift */; }; 6B4B48F32BD7BB3C007D357D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4B48F22BD7BB3C007D357D /* Token.swift */; }; 6B5E41CE2BD790F80045DBC6 /* EncryptedToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */; }; 6B66D5E72B526315006DB79D /* AddTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B66D5E62B526315006DB79D /* AddTokenView.swift */; }; @@ -75,6 +76,7 @@ 6B3BB0E12B4ED19300DCEF0B /* TokensTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensTab.swift; sourceTree = ""; }; 6B3C7A372BE764E70043FEBD /* StorageSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSetupView.swift; sourceTree = ""; }; 6B3F92A82C1B45AA004125A8 /* Vault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vault.swift; sourceTree = ""; }; + 6B3F92AA2C1C7987004125A8 /* VaultService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultService.swift; sourceTree = ""; }; 6B4B48F22BD7BB3C007D357D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedToken.swift; sourceTree = ""; }; 6B66D5E62B526315006DB79D /* AddTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTokenView.swift; sourceTree = ""; }; @@ -245,6 +247,7 @@ 6BD6D2002C11FEB4004512BF /* OTPService.swift */, 6B9D74642C14ADDC008E6582 /* StateService.swift */, 6B12B09F2C19DB7800E9ED2D /* ExportService.swift */, + 6B3F92AA2C1C7987004125A8 /* VaultService.swift */, ); path = Services; sourceTree = ""; @@ -413,6 +416,7 @@ files = ( 6B3BB0B32B4EA87C00DCEF0B /* ContentView.swift in Sources */, 6B3BB0B12B4EA87C00DCEF0B /* ChronosApp.swift in Sources */, + 6B3F92AB2C1C7987004125A8 /* VaultService.swift in Sources */, 6B12B0A22C19F1E600E9ED2D /* ExportVault.swift in Sources */, 6B7383E52B9C4230008E8867 /* Container.swift in Sources */, 6B3C7A382BE764E70043FEBD /* StorageSetupView.swift in Sources */, diff --git a/Chronos/App/Onboarding/PasswordSetupView.swift b/Chronos/App/Onboarding/PasswordSetupView.swift index bd7aa6a..36f4b69 100644 --- a/Chronos/App/Onboarding/PasswordSetupView.swift +++ b/Chronos/App/Onboarding/PasswordSetupView.swift @@ -18,8 +18,6 @@ struct PasswordSetupView: View { @FocusState private var focusedField: FocusedField? - @AppStorage(StateEnum.VAULT.rawValue) var stateVaultId: String? - let cryptoService = Container.shared.cryptoService() let stateService = Container.shared.stateService() diff --git a/Chronos/Services/Container.swift b/Chronos/Services/Container.swift index df42b03..bb6eb3d 100644 --- a/Chronos/Services/Container.swift +++ b/Chronos/Services/Container.swift @@ -10,6 +10,10 @@ extension Container { .singleton } + var vaultService: Factory { + Factory(self) { VaultService() } + } + var cryptoService: Factory { Factory(self) { CryptoService() } } diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index 7f3482f..df9a24b 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -13,10 +13,9 @@ public class CryptoService { private let logger = Logger(label: "CryptoService") private let stateService = Container.shared.stateService() + private let vaultService = Container.shared.vaultService() private let swiftDataService = Container.shared.swiftDataService() - private let defaults = UserDefaults.standard - // scrypt paramaters - n: 2^17, r: 8, p: 1 private let kdfParams = KdfParams(type: 0, n: 1 << 17, r: 8, p: 1) @@ -34,7 +33,7 @@ public class CryptoService { let keyParams = KeyParams(iv: iv, tag: encrypt.authenticationTag) - let newPasswordCrypto = ChronosCrypto(vault: stateService.vault!, key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) + let newPasswordCrypto = ChronosCrypto(vault: vaultService.getVault()!, key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) let context = ModelContext(swiftDataService.getModelContainer()) @@ -46,16 +45,7 @@ public class CryptoService { } func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore: Bool = false) async -> Bool { - let context = ModelContext(swiftDataService.getModelContainer(isRestore: isRestore)) - - // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. - guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { - logger.error("No vaults found") - return false - } - - guard let vault = vaultArr.first else { - logger.error("Empty vaultArr") + guard let vault = vaultService.getFirstVault() else { return false } @@ -88,6 +78,8 @@ public class CryptoService { if decrypt.success { stateService.masterKey = SecureBytes(bytes: decrypt.plainText) + stateService.setVaultId(vaultId: vault.vaultId!) + decrypt.plainText.removeAll() return true } else { @@ -110,7 +102,7 @@ extension CryptoService { let tokenJson = try JSONEncoder().encode(token) let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(tokenJson), key: Array(stateService.masterKey), iv: iv, authenticationHeader: header) - return EncryptedToken(vault: stateService.vault!, encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) + return EncryptedToken(vault: vaultService.getVault()!, encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) } catch { fatalError(error.localizedDescription) } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index 84d1534..3b537be 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -2,7 +2,7 @@ import Factory import Foundation enum StateEnum: String { - case VAULT + case VAULT_ID case ICLOUD_BACKUP_ENABLED case BIOMETRICS_AUTH_ENABLED @@ -16,8 +16,21 @@ public class StateService { var masterKey: SecureBytes = .init(bytes: []) var vault: Vault? - + + func setVaultId(vaultId: UUID) { + defaults.setValue(vaultId.uuidString, forKey: StateEnum.VAULT_ID.rawValue) + } + + func getVaultId() -> UUID? { + guard let vaultIdStr = defaults.string(forKey: StateEnum.VAULT_ID.rawValue) else { + return nil + } + + return UUID(uuidString: vaultIdStr) + } + func resetAllStates() { + defaults.setValue(false, forKey: StateEnum.VAULT_ID.rawValue) defaults.setValue(false, forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.ONBOARDING_COMPLETED.rawValue) diff --git a/Chronos/Services/VaultService.swift b/Chronos/Services/VaultService.swift new file mode 100644 index 0000000..2075d56 --- /dev/null +++ b/Chronos/Services/VaultService.swift @@ -0,0 +1,48 @@ +import Factory +import Foundation +import SwiftData +import Logging + +public class VaultService { + private let logger = Logger(label: "VaultService") + + private let stateService = Container.shared.stateService() + private let swiftDataService = Container.shared.swiftDataService() + + // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. + func getFirstVault() -> Vault? { + let context = ModelContext(swiftDataService.getCloudModelContainer()) + + guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { + logger.error("No vaults found") + return nil + } + + guard let vault = vaultArr.first else { + logger.error("Empty vaultArr") + return nil + } + + stateService.setVaultId(vaultId: vault.vaultId!) + + return vault + } + + func getVault() -> Vault? { + guard let vaultId: UUID = stateService.getVaultId() else { + logger.error("vaultId not found in AppStorage") + return nil + } + + let context = ModelContext(swiftDataService.getModelContainer()) + + let predicate = #Predicate { $0.vaultId == vaultId } + + guard let vaultArr = try? context.fetch(FetchDescriptor(predicate: predicate)) else { + logger.error("No vaults found") + return nil + } + + return vaultArr.first + } +} From 9b7aa564bcb0b1cb09fc36336f32a56ba896d73a Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 22:22:32 +0800 Subject: [PATCH 5/9] lint --- Chronos/Services/Container.swift | 2 +- Chronos/Services/CryptoService.swift | 4 ++-- Chronos/Services/StateService.swift | 8 ++++---- Chronos/Services/VaultService.swift | 14 +++++++------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Chronos/Services/Container.swift b/Chronos/Services/Container.swift index bb6eb3d..759350a 100644 --- a/Chronos/Services/Container.swift +++ b/Chronos/Services/Container.swift @@ -13,7 +13,7 @@ extension Container { var vaultService: Factory { Factory(self) { VaultService() } } - + var cryptoService: Factory { Factory(self) { CryptoService() } } diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index df9a24b..17c093c 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -44,7 +44,7 @@ public class CryptoService { } } - func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore: Bool = false) async -> Bool { + func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore _: Bool = false) async -> Bool { guard let vault = vaultService.getFirstVault() else { return false } @@ -79,7 +79,7 @@ public class CryptoService { if decrypt.success { stateService.masterKey = SecureBytes(bytes: decrypt.plainText) stateService.setVaultId(vaultId: vault.vaultId!) - + decrypt.plainText.removeAll() return true } else { diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index 3b537be..a6eacfb 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -16,19 +16,19 @@ public class StateService { var masterKey: SecureBytes = .init(bytes: []) var vault: Vault? - + func setVaultId(vaultId: UUID) { defaults.setValue(vaultId.uuidString, forKey: StateEnum.VAULT_ID.rawValue) } - + func getVaultId() -> UUID? { guard let vaultIdStr = defaults.string(forKey: StateEnum.VAULT_ID.rawValue) else { return nil } - + return UUID(uuidString: vaultIdStr) } - + func resetAllStates() { defaults.setValue(false, forKey: StateEnum.VAULT_ID.rawValue) defaults.setValue(false, forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) diff --git a/Chronos/Services/VaultService.swift b/Chronos/Services/VaultService.swift index 2075d56..3097527 100644 --- a/Chronos/Services/VaultService.swift +++ b/Chronos/Services/VaultService.swift @@ -1,14 +1,14 @@ import Factory import Foundation -import SwiftData import Logging +import SwiftData public class VaultService { private let logger = Logger(label: "VaultService") private let stateService = Container.shared.stateService() private let swiftDataService = Container.shared.swiftDataService() - + // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. func getFirstVault() -> Vault? { let context = ModelContext(swiftDataService.getCloudModelContainer()) @@ -17,17 +17,17 @@ public class VaultService { logger.error("No vaults found") return nil } - + guard let vault = vaultArr.first else { logger.error("Empty vaultArr") return nil } - + stateService.setVaultId(vaultId: vault.vaultId!) - + return vault } - + func getVault() -> Vault? { guard let vaultId: UUID = stateService.getVaultId() else { logger.error("vaultId not found in AppStorage") @@ -42,7 +42,7 @@ public class VaultService { logger.error("No vaults found") return nil } - + return vaultArr.first } } From 2bb5648eb30143dd9da7dacb2d7435df62962829 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 22:23:24 +0800 Subject: [PATCH 6/9] remvoed unused var --- Chronos/App/Onboarding/PasswordSetupView.swift | 2 -- Chronos/Services/StateService.swift | 1 - 2 files changed, 3 deletions(-) diff --git a/Chronos/App/Onboarding/PasswordSetupView.swift b/Chronos/App/Onboarding/PasswordSetupView.swift index 36f4b69..2272521 100644 --- a/Chronos/App/Onboarding/PasswordSetupView.swift +++ b/Chronos/App/Onboarding/PasswordSetupView.swift @@ -105,8 +105,6 @@ extension PasswordSetupView { let vault = Vault(vaultId: UUID(), createdAt: Date(), chronosCryptos: [], encryptedTokens: []) modelContext.insert(vault) - stateService.vault = vault - await cryptoService.wrapMasterKeyWithUserPassword(password: Array(password.utf8)) nextBtnPressed = true } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index a6eacfb..a699624 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -15,7 +15,6 @@ public class StateService { private let defaults = UserDefaults.standard var masterKey: SecureBytes = .init(bytes: []) - var vault: Vault? func setVaultId(vaultId: UUID) { defaults.setValue(vaultId.uuidString, forKey: StateEnum.VAULT_ID.rawValue) From d37eb5a521a9160fe6a74520b55d96d67f01d80b Mon Sep 17 00:00:00 2001 From: Joel-David Date: Fri, 14 Jun 2024 23:51:06 +0800 Subject: [PATCH 7/9] WIP. Having issues with container --- .../xcshareddata/swiftpm/Package.resolved | 78 ------------------- .../App/Onboarding/PasswordSetupView.swift | 3 + .../App/Onboarding/RestoreBackupView.swift | 8 +- Chronos/App/Privacy/PrivacyView.swift | 4 - Chronos/App/Tabs/Tokens/TokensTab.swift | 14 +++- Chronos/Services/StateService.swift | 34 +++++--- Chronos/Services/SwiftDataService.swift | 6 +- Chronos/Services/VaultService.swift | 4 +- 8 files changed, 47 insertions(+), 104 deletions(-) delete mode 100644 Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 3547a74..0000000 --- a/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,78 +0,0 @@ -{ - "originHash" : "fce3d4a72e08c7c2acd350debe307850f30913bd1e53ad0114fb221b3f8ffe73", - "pins" : [ - { - "identity" : "alertkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparrowcode/AlertKit", - "state" : { - "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", - "version" : "5.1.9" - } - }, - { - "identity" : "codescanner", - "kind" : "remoteSourceControl", - "location" : "https://github.com/twostraws/CodeScanner.git", - "state" : { - "revision" : "34da57fb63b47add20de8a85da58191523ccce57", - "version" : "2.5.0" - } - }, - { - "identity" : "cryptoswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", - "state" : { - "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", - "version" : "1.8.2" - } - }, - { - "identity" : "factory", - "kind" : "remoteSourceControl", - "location" : "https://github.com/hmlongco/Factory", - "state" : { - "revision" : "587995f7d5cc667951d635fbf6b4252324ba0439", - "version" : "2.3.2" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", - "version" : "3.4.0" - } - }, - { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", - "version" : "1.5.4" - } - }, - { - "identity" : "swiftotp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/lachlanbell/SwiftOTP.git", - "state" : { - "revision" : "9660551ea3df153c3cbacfa34ac3abbec73a8b84", - "version" : "3.0.2" - } - }, - { - "identity" : "valet", - "kind" : "remoteSourceControl", - "location" : "https://github.com/square/Valet", - "state" : { - "revision" : "3df8eaa90e1fa0d80830733cdc3c9e098146af3d", - "version" : "4.3.0" - } - } - ], - "version" : 3 -} diff --git a/Chronos/App/Onboarding/PasswordSetupView.swift b/Chronos/App/Onboarding/PasswordSetupView.swift index 2272521..73ee6ff 100644 --- a/Chronos/App/Onboarding/PasswordSetupView.swift +++ b/Chronos/App/Onboarding/PasswordSetupView.swift @@ -104,6 +104,9 @@ extension PasswordSetupView { let vault = Vault(vaultId: UUID(), createdAt: Date(), chronosCryptos: [], encryptedTokens: []) modelContext.insert(vault) + try? modelContext.save() + + stateService.setVaultId(vaultId: vault.vaultId!) await cryptoService.wrapMasterKeyWithUserPassword(password: Array(password.utf8)) nextBtnPressed = true diff --git a/Chronos/App/Onboarding/RestoreBackupView.swift b/Chronos/App/Onboarding/RestoreBackupView.swift index 53f5acb..92b4137 100644 --- a/Chronos/App/Onboarding/RestoreBackupView.swift +++ b/Chronos/App/Onboarding/RestoreBackupView.swift @@ -13,6 +13,8 @@ struct RestoreBackupView: View { @FocusState private var focusedField: FocusedField? let cryptoService = Container.shared.cryptoService() + let vaultService = Container.shared.vaultService() + let stateService = Container.shared.stateService() var body: some View { VStack { @@ -58,11 +60,13 @@ struct RestoreBackupView: View { restoreBtnPressed = false if passwordVerified { + let vault = vaultService.getFirstVault() + stateService.setVaultId(vaultId: vault!.vaultId!) + isICloudEnabled = true } else { passwordInvalid = true - let notificationGenerator = UINotificationFeedbackGenerator() - notificationGenerator.notificationOccurred(.error) + UINotificationFeedbackGenerator().notificationOccurred(.error) } } } label: { diff --git a/Chronos/App/Privacy/PrivacyView.swift b/Chronos/App/Privacy/PrivacyView.swift index b40a31b..495c698 100644 --- a/Chronos/App/Privacy/PrivacyView.swift +++ b/Chronos/App/Privacy/PrivacyView.swift @@ -17,7 +17,3 @@ struct PrivacyView: View { .background(Color(red: 0.04, green: 0, blue: 0.11)) } } - -#Preview { - PrivacyView() -} diff --git a/Chronos/App/Tabs/Tokens/TokensTab.swift b/Chronos/App/Tabs/Tokens/TokensTab.swift index 11fa975..4790f55 100644 --- a/Chronos/App/Tabs/Tokens/TokensTab.swift +++ b/Chronos/App/Tabs/Tokens/TokensTab.swift @@ -4,7 +4,7 @@ import SwiftUI struct TokensTab: View { @Query(sort: \EncryptedToken.createdAt) private var encyptedTokens: [EncryptedToken] - + @State private var showTokenAddSheet = false @State private var showTokenUpdateSheet = false @State private var showTokenDeleteSheet = false @@ -13,11 +13,19 @@ struct TokensTab: View { @State private var selectedTokenForUpdate: Token? = nil @State var detentHeight: CGFloat = 0 + let stateService = Container.shared.stateService() + + private var filteredEncyptedTokens: [EncryptedToken] { + return encyptedTokens.compactMap { encToken in + return encToken.vault?.vaultId == stateService.getVaultId() ? encToken : nil + } + } + var body: some View { ZStack { NavigationStack { ScrollViewReader { _ in - List(encyptedTokens) { encyptedToken in + List(filteredEncyptedTokens) { encyptedToken in TokenRowView(tokenRowViewModel: TokenRowViewModel(encyptedToken: encyptedToken)) } .listStyle(.plain) @@ -40,6 +48,8 @@ struct TokensTab: View { .presentationDetents([.height(self.detentHeight)]) } } + }.onAppear { + stateService.getAllStates() } } } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index a699624..95f0cba 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -3,43 +3,53 @@ import Foundation enum StateEnum: String { case VAULT_ID - + case ICLOUD_BACKUP_ENABLED case BIOMETRICS_AUTH_ENABLED case ONBOARDING_COMPLETED - + case LAST_BIOMETRICS_AUTH_ATTEMPT } public class StateService { private let defaults = UserDefaults.standard - + var masterKey: SecureBytes = .init(bytes: []) - + func setVaultId(vaultId: UUID) { defaults.setValue(vaultId.uuidString, forKey: StateEnum.VAULT_ID.rawValue) } - + func getVaultId() -> UUID? { guard let vaultIdStr = defaults.string(forKey: StateEnum.VAULT_ID.rawValue) else { return nil } - + return UUID(uuidString: vaultIdStr) } - + func resetAllStates() { - defaults.setValue(false, forKey: StateEnum.VAULT_ID.rawValue) + defaults.setValue(nil, forKey: StateEnum.VAULT_ID.rawValue) + defaults.setValue(false, forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.ONBOARDING_COMPLETED.rawValue) - - defaults.setValue(0, forKey: StateEnum.LAST_BIOMETRICS_AUTH_ATTEMPT.rawValue) - + + defaults.setValue(Date().timeIntervalSince1970, forKey: StateEnum.LAST_BIOMETRICS_AUTH_ATTEMPT.rawValue) + masterKey.clear() } - + func clearMasterKey() { masterKey.clear() } + + func getAllStates() { + print("DUCK") + print(defaults.value(forKey: StateEnum.VAULT_ID.rawValue)) + print(defaults.value(forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue)) + print(defaults.value(forKey: StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue)) + print(defaults.value(forKey: StateEnum.ONBOARDING_COMPLETED.rawValue)) + print(defaults.value(forKey: StateEnum.LAST_BIOMETRICS_AUTH_ATTEMPT.rawValue)) + } } diff --git a/Chronos/Services/SwiftDataService.swift b/Chronos/Services/SwiftDataService.swift index 50349ad..ff1ea14 100644 --- a/Chronos/Services/SwiftDataService.swift +++ b/Chronos/Services/SwiftDataService.swift @@ -3,15 +3,15 @@ import Logging import SwiftData public class SwiftDataService { - let logger = Logger(label: "SwiftDataModelSingleton") + private let logger = Logger(label: "SwiftDataModelSingleton") - let defaults = UserDefaults.standard + private let defaults = UserDefaults.standard private lazy var localModelContainer: ModelContainer = setupModelContainer(storeName: "localChronos.sqlite", cloudKitDatabase: .none) private lazy var cloudModelContainer: ModelContainer = setupModelContainer(storeName: "onlineChronos.sqlite", cloudKitDatabase: .automatic) - let schema = Schema([Vault.self, ChronosCrypto.self, EncryptedToken.self]) + private let schema = Schema([Vault.self, ChronosCrypto.self, EncryptedToken.self]) init() { _ = localModelContainer diff --git a/Chronos/Services/VaultService.swift b/Chronos/Services/VaultService.swift index 3097527..567d0a5 100644 --- a/Chronos/Services/VaultService.swift +++ b/Chronos/Services/VaultService.swift @@ -11,7 +11,7 @@ public class VaultService { // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. func getFirstVault() -> Vault? { - let context = ModelContext(swiftDataService.getCloudModelContainer()) + let context = ModelContext(swiftDataService.getModelContainer()) guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { logger.error("No vaults found") @@ -23,8 +23,6 @@ public class VaultService { return nil } - stateService.setVaultId(vaultId: vault.vaultId!) - return vault } From 5f65975cbb762d1feeb342481561c28345cff6f5 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Sat, 15 Jun 2024 00:31:23 +0800 Subject: [PATCH 8/9] Fixed restore --- .../xcshareddata/swiftpm/Package.resolved | 78 +++++++++++++++++++ .../App/Onboarding/RestoreBackupView.swift | 2 +- Chronos/App/Tabs/Tokens/TokensTab.swift | 8 +- Chronos/Services/CryptoService.swift | 4 +- Chronos/Services/StateService.swift | 24 +++--- Chronos/Services/VaultService.swift | 4 +- 6 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..3547a74 --- /dev/null +++ b/Chronos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,78 @@ +{ + "originHash" : "fce3d4a72e08c7c2acd350debe307850f30913bd1e53ad0114fb221b3f8ffe73", + "pins" : [ + { + "identity" : "alertkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparrowcode/AlertKit", + "state" : { + "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", + "version" : "5.1.9" + } + }, + { + "identity" : "codescanner", + "kind" : "remoteSourceControl", + "location" : "https://github.com/twostraws/CodeScanner.git", + "state" : { + "revision" : "34da57fb63b47add20de8a85da58191523ccce57", + "version" : "2.5.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" + } + }, + { + "identity" : "factory", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hmlongco/Factory", + "state" : { + "revision" : "587995f7d5cc667951d635fbf6b4252324ba0439", + "version" : "2.3.2" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", + "version" : "3.4.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" + } + }, + { + "identity" : "swiftotp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lachlanbell/SwiftOTP.git", + "state" : { + "revision" : "9660551ea3df153c3cbacfa34ac3abbec73a8b84", + "version" : "3.0.2" + } + }, + { + "identity" : "valet", + "kind" : "remoteSourceControl", + "location" : "https://github.com/square/Valet", + "state" : { + "revision" : "3df8eaa90e1fa0d80830733cdc3c9e098146af3d", + "version" : "4.3.0" + } + } + ], + "version" : 3 +} diff --git a/Chronos/App/Onboarding/RestoreBackupView.swift b/Chronos/App/Onboarding/RestoreBackupView.swift index 92b4137..5fc8699 100644 --- a/Chronos/App/Onboarding/RestoreBackupView.swift +++ b/Chronos/App/Onboarding/RestoreBackupView.swift @@ -60,7 +60,7 @@ struct RestoreBackupView: View { restoreBtnPressed = false if passwordVerified { - let vault = vaultService.getFirstVault() + let vault = vaultService.getFirstVault(isRestore: true) stateService.setVaultId(vaultId: vault!.vaultId!) isICloudEnabled = true diff --git a/Chronos/App/Tabs/Tokens/TokensTab.swift b/Chronos/App/Tabs/Tokens/TokensTab.swift index 4790f55..5ddd910 100644 --- a/Chronos/App/Tabs/Tokens/TokensTab.swift +++ b/Chronos/App/Tabs/Tokens/TokensTab.swift @@ -4,7 +4,7 @@ import SwiftUI struct TokensTab: View { @Query(sort: \EncryptedToken.createdAt) private var encyptedTokens: [EncryptedToken] - + @State private var showTokenAddSheet = false @State private var showTokenUpdateSheet = false @State private var showTokenDeleteSheet = false @@ -14,13 +14,13 @@ struct TokensTab: View { @State var detentHeight: CGFloat = 0 let stateService = Container.shared.stateService() - + private var filteredEncyptedTokens: [EncryptedToken] { return encyptedTokens.compactMap { encToken in - return encToken.vault?.vaultId == stateService.getVaultId() ? encToken : nil + encToken.vault?.vaultId == stateService.getVaultId() ? encToken : nil } } - + var body: some View { ZStack { NavigationStack { diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index 17c093c..d8602de 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -44,8 +44,8 @@ public class CryptoService { } } - func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore _: Bool = false) async -> Bool { - guard let vault = vaultService.getFirstVault() else { + func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore: Bool = false) async -> Bool { + guard let vault = vaultService.getFirstVault(isRestore: isRestore) else { return false } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index 95f0cba..7e43f7a 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -3,47 +3,47 @@ import Foundation enum StateEnum: String { case VAULT_ID - + case ICLOUD_BACKUP_ENABLED case BIOMETRICS_AUTH_ENABLED case ONBOARDING_COMPLETED - + case LAST_BIOMETRICS_AUTH_ATTEMPT } public class StateService { private let defaults = UserDefaults.standard - + var masterKey: SecureBytes = .init(bytes: []) - + func setVaultId(vaultId: UUID) { defaults.setValue(vaultId.uuidString, forKey: StateEnum.VAULT_ID.rawValue) } - + func getVaultId() -> UUID? { guard let vaultIdStr = defaults.string(forKey: StateEnum.VAULT_ID.rawValue) else { return nil } - + return UUID(uuidString: vaultIdStr) } - + func resetAllStates() { defaults.setValue(nil, forKey: StateEnum.VAULT_ID.rawValue) - + defaults.setValue(false, forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.ONBOARDING_COMPLETED.rawValue) - + defaults.setValue(Date().timeIntervalSince1970, forKey: StateEnum.LAST_BIOMETRICS_AUTH_ATTEMPT.rawValue) - + masterKey.clear() } - + func clearMasterKey() { masterKey.clear() } - + func getAllStates() { print("DUCK") print(defaults.value(forKey: StateEnum.VAULT_ID.rawValue)) diff --git a/Chronos/Services/VaultService.swift b/Chronos/Services/VaultService.swift index 567d0a5..1848d24 100644 --- a/Chronos/Services/VaultService.swift +++ b/Chronos/Services/VaultService.swift @@ -10,8 +10,8 @@ public class VaultService { private let swiftDataService = Container.shared.swiftDataService() // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. - func getFirstVault() -> Vault? { - let context = ModelContext(swiftDataService.getModelContainer()) + func getFirstVault(isRestore: Bool) -> Vault? { + let context = ModelContext(swiftDataService.getModelContainer(isRestore: isRestore)) guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { logger.error("No vaults found") From eb1914336925f75d4c75cc499fd34372cd281036 Mon Sep 17 00:00:00 2001 From: Joel-David Date: Sat, 15 Jun 2024 10:34:43 +0800 Subject: [PATCH 9/9] remove debug log --- Chronos/App/Tabs/Tokens/TokensTab.swift | 2 -- Chronos/Services/StateService.swift | 9 --------- 2 files changed, 11 deletions(-) diff --git a/Chronos/App/Tabs/Tokens/TokensTab.swift b/Chronos/App/Tabs/Tokens/TokensTab.swift index 5ddd910..ff71fd2 100644 --- a/Chronos/App/Tabs/Tokens/TokensTab.swift +++ b/Chronos/App/Tabs/Tokens/TokensTab.swift @@ -48,8 +48,6 @@ struct TokensTab: View { .presentationDetents([.height(self.detentHeight)]) } } - }.onAppear { - stateService.getAllStates() } } } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index 7e43f7a..26bb965 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -43,13 +43,4 @@ public class StateService { func clearMasterKey() { masterKey.clear() } - - func getAllStates() { - print("DUCK") - print(defaults.value(forKey: StateEnum.VAULT_ID.rawValue)) - print(defaults.value(forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue)) - print(defaults.value(forKey: StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue)) - print(defaults.value(forKey: StateEnum.ONBOARDING_COMPLETED.rawValue)) - print(defaults.value(forKey: StateEnum.LAST_BIOMETRICS_AUTH_ATTEMPT.rawValue)) - } }