MasterKeyUtil.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. //
  2. // SecureStorage.swift
  3. // Pods
  4. //
  5. // Created by Qindi on 02/12/24.
  6. //
  7. import CryptoKit
  8. import LocalAuthentication
  9. public class MasterKeyUtil {
  10. static let shared = MasterKeyUtil()
  11. private let keyAlias = "_iosx_security_master_key"
  12. private let prefsKeyAlias = "_iosx_security_master_key_easysoft_"
  13. private init() {}
  14. func generateAndStoreKey() throws {
  15. if try isKeyExists(keyAliasCode: keyAlias) {
  16. // print("Master Key already exists, skipping generation.")
  17. return
  18. }
  19. let key = SymmetricKey(size: .bits256)
  20. let keyData = key.withUnsafeBytes { Data($0) }
  21. let query: [String: Any] = [
  22. kSecClass as String: kSecClassKey,
  23. kSecAttrApplicationTag as String: keyAlias,
  24. kSecValueData as String: keyData,
  25. kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
  26. ]
  27. SecItemDelete(query as CFDictionary) // Remove if it exists
  28. let status = SecItemAdd(query as CFDictionary, nil)
  29. guard status == errSecSuccess else {
  30. throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
  31. }
  32. }
  33. func generateAndStorePrefsKey() throws {
  34. if try isKeyExists(keyAliasCode: prefsKeyAlias) {
  35. // print("Prefs Key already exists, skipping generation.")
  36. return
  37. }
  38. let key = SymmetricKey(size: .bits256)
  39. let keyData = key.withUnsafeBytes { Data($0) }
  40. let query: [String: Any] = [
  41. kSecClass as String: kSecClassKey,
  42. kSecAttrApplicationTag as String: prefsKeyAlias,
  43. kSecValueData as String: keyData,
  44. kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
  45. ]
  46. SecItemDelete(query as CFDictionary) // Remove if it exists
  47. let status = SecItemAdd(query as CFDictionary, nil)
  48. guard status == errSecSuccess else {
  49. throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
  50. }
  51. }
  52. func isDeviceNotSecure() -> Bool {
  53. let context = LAContext()
  54. var error: NSError?
  55. if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) || Utils.shouldRequestAuthentication() {
  56. return true
  57. } else {
  58. return false
  59. }
  60. }
  61. func isKeyExists(keyAliasCode: String) throws -> Bool {
  62. let query: [String: Any] = [
  63. kSecClass as String: kSecClassKey,
  64. kSecAttrApplicationTag as String: keyAliasCode,
  65. kSecReturnData as String: false // We only check existence, not retrieve data
  66. ]
  67. let status = SecItemCopyMatching(query as CFDictionary, nil)
  68. if status == errSecItemNotFound {
  69. return false
  70. } else if status == errSecSuccess {
  71. return true
  72. } else {
  73. throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
  74. }
  75. }
  76. func getMasterKey() throws -> SymmetricKey {
  77. if (Nexilis.checkingAccess(key: "authentication") && isDeviceNotSecure()) {
  78. var result = false
  79. Nexilis.dispatch = DispatchGroup()
  80. Nexilis.dispatch?.enter()
  81. Utils.authenticateWithBiometrics { success, errorMessage in
  82. if success {
  83. print("Access granted!")
  84. result = true
  85. } else {
  86. print("Access denied: \(errorMessage ?? "Unknown error")")
  87. }
  88. if let dispatch = Nexilis.dispatch {
  89. dispatch.leave()
  90. }
  91. }
  92. Nexilis.dispatch?.wait()
  93. Nexilis.dispatch = nil
  94. if !result {
  95. Utils.showAlert(title: "Failed to get Master Key".localized(), message: "Biometric authentication hasn't been set up/Biometric invalid.".localized())
  96. throw NSError(domain: "KeychainError", code: -99, userInfo: nil)
  97. }
  98. }
  99. let query: [String: Any] = [
  100. kSecClass as String: kSecClassKey,
  101. kSecAttrApplicationTag as String: keyAlias,
  102. kSecReturnData as String: true
  103. ]
  104. var item: CFTypeRef?
  105. let status = SecItemCopyMatching(query as CFDictionary, &item)
  106. guard status == errSecSuccess else {
  107. throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
  108. }
  109. guard let keyData = item as? Data else {
  110. throw NSError(domain: "KeyRetrievalError", code: -1, userInfo: nil)
  111. }
  112. return SymmetricKey(data: keyData)
  113. }
  114. func getPrefsKey() throws -> SymmetricKey {
  115. let query: [String: Any] = [
  116. kSecClass as String: kSecClassKey,
  117. kSecAttrApplicationTag as String: prefsKeyAlias,
  118. kSecReturnData as String: true
  119. ]
  120. var item: CFTypeRef?
  121. let status = SecItemCopyMatching(query as CFDictionary, &item)
  122. guard status == errSecSuccess else {
  123. throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
  124. }
  125. guard let keyData = item as? Data else {
  126. throw NSError(domain: "KeyRetrievalError", code: -1, userInfo: nil)
  127. }
  128. return SymmetricKey(data: keyData)
  129. }
  130. func encryptP(data: Data) throws -> Data {
  131. let key = try getPrefsKey()
  132. let sealedBox = try AES.GCM.seal(data, using: key)
  133. return sealedBox.combined!
  134. }
  135. func decryptP(data: Data) throws -> Data {
  136. let key = try getPrefsKey()
  137. let sealedBox = try AES.GCM.SealedBox(combined: data)
  138. return try AES.GCM.open(sealedBox, using: key)
  139. }
  140. func encryptD(data: Data) throws -> Data {
  141. let key = try getMasterKey()
  142. let sealedBox = try AES.GCM.seal(data, using: key)
  143. return sealedBox.combined!
  144. }
  145. // Decrypt data
  146. func decryptD(data: Data) throws -> Data {
  147. let key = try getMasterKey()
  148. let sealedBox = try AES.GCM.SealedBox(combined: data)
  149. return try AES.GCM.open(sealedBox, using: key)
  150. }
  151. }