NCManageE2EE.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. //
  2. // NCManageE2EE.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 17/11/22.
  6. // Copyright © 2022 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import SwiftUI
  24. import NextcloudKit
  25. import TOPasscodeViewController
  26. import LocalAuthentication
  27. @objc class NCManageE2EEInterface: NSObject {
  28. @objc func makeShipDetailsUI(account: String, rootViewController: UIViewController?) -> UIViewController {
  29. let details = NCViewE2EE(account: account, rootViewController: rootViewController)
  30. let vc = UIHostingController(rootView: details)
  31. vc.title = NSLocalizedString("_e2e_settings_", comment: "")
  32. return vc
  33. }
  34. }
  35. class NCManageE2EE: NSObject, ObservableObject, NCEndToEndInitializeDelegate, TOPasscodeViewControllerDelegate {
  36. let endToEndInitialize = NCEndToEndInitialize()
  37. let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
  38. var passcodeType = ""
  39. @Published var rootViewController: UIViewController?
  40. @Published var isEndToEndEnabled: Bool = false
  41. @Published var statusOfService: String = NSLocalizedString("_status_in_progress_", comment: "")
  42. init(rootViewController: UIViewController?) {
  43. super.init()
  44. self.rootViewController = rootViewController
  45. endToEndInitialize.delegate = self
  46. isEndToEndEnabled = NCKeychain().isEndToEndEnabled(account: appDelegate.account)
  47. if isEndToEndEnabled {
  48. statusOfService = NSLocalizedString("_status_e2ee_configured_", comment: "")
  49. } else {
  50. endToEndInitialize.statusOfService { error in
  51. if error == .success {
  52. self.statusOfService = NSLocalizedString("_status_e2ee_on_server_", comment: "")
  53. } else {
  54. self.statusOfService = NSLocalizedString("_status_e2ee_not_setup_", comment: "")
  55. }
  56. }
  57. }
  58. }
  59. // MARK: - Delegate
  60. func endToEndInitializeSuccess(metadata: tableMetadata?) {
  61. isEndToEndEnabled = true
  62. }
  63. // MARK: - Passcode
  64. @objc func requestPasscodeType(_ passcodeType: String) {
  65. let laContext = LAContext()
  66. var error: NSError?
  67. let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true)
  68. passcodeViewController.delegate = self
  69. passcodeViewController.keypadButtonShowLettering = false
  70. if NCKeychain().touchFaceID, laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
  71. if error == nil {
  72. if laContext.biometryType == .faceID {
  73. passcodeViewController.biometryType = .faceID
  74. passcodeViewController.allowBiometricValidation = true
  75. } else if laContext.biometryType == .touchID {
  76. passcodeViewController.biometryType = .touchID
  77. }
  78. passcodeViewController.allowBiometricValidation = true
  79. passcodeViewController.automaticallyPromptForBiometricValidation = true
  80. }
  81. }
  82. self.passcodeType = passcodeType
  83. rootViewController?.present(passcodeViewController, animated: true)
  84. }
  85. @objc func correctPasscode() {
  86. switch self.passcodeType {
  87. case "startE2E":
  88. endToEndInitialize.initEndToEndEncryption(viewController: rootViewController, metadata: nil)
  89. case "readPassphrase":
  90. if let e2ePassphrase = NCKeychain().getEndToEndPassphrase(account: appDelegate.account) {
  91. print("[INFO]Passphrase: " + e2ePassphrase)
  92. let message = "\n" + NSLocalizedString("_e2e_settings_the_passphrase_is_", comment: "") + "\n\n\n" + e2ePassphrase
  93. let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
  94. alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
  95. alertController.addAction(UIAlertAction(title: NSLocalizedString("_copy_passphrase_", comment: ""), style: .default, handler: { _ in
  96. UIPasteboard.general.string = e2ePassphrase
  97. }))
  98. rootViewController?.present(alertController, animated: true)
  99. }
  100. case "removeLocallyEncryption":
  101. let alertController = UIAlertController(title: NSLocalizedString("_e2e_settings_remove_", comment: ""), message: NSLocalizedString("_e2e_settings_remove_message_", comment: ""), preferredStyle: .alert)
  102. alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_", comment: ""), style: .default, handler: { _ in
  103. NCKeychain().clearAllKeysEndToEnd(account: self.appDelegate.account)
  104. self.isEndToEndEnabled = NCKeychain().isEndToEndEnabled(account: self.appDelegate.account)
  105. }))
  106. alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default, handler: { _ in }))
  107. rootViewController?.present(alertController, animated: true)
  108. default:
  109. break
  110. }
  111. }
  112. func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
  113. if code == NCKeychain().passcode {
  114. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  115. self.correctPasscode()
  116. }
  117. return true
  118. } else {
  119. return false
  120. }
  121. }
  122. func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) {
  123. LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, _ in
  124. if success {
  125. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  126. passcodeViewController.dismiss(animated: true)
  127. self.correctPasscode()
  128. }
  129. }
  130. }
  131. }
  132. func didTapCancel(in passcodeViewController: TOPasscodeViewController) {
  133. passcodeViewController.dismiss(animated: true)
  134. }
  135. }
  136. // MARK: Views
  137. struct NCViewE2EE: View {
  138. @ObservedObject var manageE2EE: NCManageE2EE
  139. @State var account: String
  140. @State var rootViewController: UIViewController?
  141. init(account: String, rootViewController: UIViewController?) {
  142. self.manageE2EE = NCManageE2EE(rootViewController: rootViewController)
  143. self.account = account
  144. self.rootViewController = rootViewController
  145. }
  146. var body: some View {
  147. VStack {
  148. if manageE2EE.isEndToEndEnabled {
  149. List {
  150. Section(header: Text(""), footer: Text(manageE2EE.statusOfService + "\n\n" + "End-to-End Encryption " + NCGlobal.shared.capabilityE2EEApiVersion)) {
  151. Label {
  152. Text(NSLocalizedString("_e2e_settings_activated_", comment: ""))
  153. } icon: {
  154. Image(systemName: "checkmark.circle.fill")
  155. .resizable()
  156. .scaledToFit()
  157. .font(Font.system(.body).weight(.light))
  158. .foregroundColor(.green)
  159. }
  160. }
  161. HStack {
  162. Label {
  163. Text(NSLocalizedString("_e2e_settings_read_passphrase_", comment: ""))
  164. } icon: {
  165. Image(systemName: "eye")
  166. .resizable()
  167. .scaledToFit()
  168. .font(Font.system(.body).weight(.light))
  169. .foregroundColor(.black)
  170. }
  171. Spacer()
  172. }
  173. .contentShape(Rectangle())
  174. .onTapGesture {
  175. if NCKeychain().passcode != nil {
  176. manageE2EE.requestPasscodeType("readPassphrase")
  177. } else {
  178. NCContentPresenter().showInfo(error: NKError(errorCode: 0, errorDescription: "_e2e_settings_lock_not_active_"))
  179. }
  180. }
  181. HStack {
  182. Label {
  183. Text(NSLocalizedString("_e2e_settings_remove_", comment: ""))
  184. } icon: {
  185. Image(systemName: "trash")
  186. .resizable()
  187. .scaledToFit()
  188. .font(Font.system(.body).weight(.light))
  189. .foregroundColor(.red)
  190. }
  191. Spacer()
  192. }
  193. .contentShape(Rectangle())
  194. .onTapGesture {
  195. if NCKeychain().passcode != nil {
  196. manageE2EE.requestPasscodeType("removeLocallyEncryption")
  197. } else {
  198. NCContentPresenter().showInfo(error: NKError(errorCode: 0, errorDescription: "_e2e_settings_lock_not_active_"))
  199. }
  200. }
  201. #if DEBUG
  202. DeleteCerificateSection()
  203. #endif
  204. }
  205. } else {
  206. List {
  207. Section(header: Text(""), footer: Text(manageE2EE.statusOfService + "\n\n" + "End-to-End Encryption " + NCGlobal.shared.capabilityE2EEApiVersion)) {
  208. HStack {
  209. Label {
  210. Text(NSLocalizedString("_e2e_settings_start_", comment: ""))
  211. } icon: {
  212. Image(systemName: "play.circle")
  213. .resizable()
  214. .scaledToFit()
  215. .font(Font.system(.body).weight(.light))
  216. .foregroundColor(.green)
  217. }
  218. Spacer()
  219. }
  220. .contentShape(Rectangle())
  221. .onTapGesture {
  222. if let passcode = NCKeychain().passcode {
  223. manageE2EE.requestPasscodeType("startE2E")
  224. } else {
  225. NCContentPresenter().showInfo(error: NKError(errorCode: 0, errorDescription: "_e2e_settings_lock_not_active_"))
  226. }
  227. }
  228. }
  229. #if DEBUG
  230. DeleteCerificateSection()
  231. #endif
  232. }
  233. }
  234. }
  235. .background(Color(UIColor.systemGroupedBackground))
  236. }
  237. }
  238. struct DeleteCerificateSection: View {
  239. var body: some View {
  240. Section(header: Text("Delete Server keys"), footer: Text("Available only in debug mode")) {
  241. HStack {
  242. Label {
  243. Text("Delete Certificate")
  244. } icon: {
  245. Image(systemName: "exclamationmark.triangle")
  246. .resizable()
  247. .scaledToFit()
  248. .font(Font.system(.body).weight(.light))
  249. .foregroundColor(Color(NCBrandColor.shared.textColor2))
  250. }
  251. Spacer()
  252. }
  253. .contentShape(Rectangle())
  254. .onTapGesture {
  255. NextcloudKit.shared.deleteE2EECertificate { _, error in
  256. if error == .success {
  257. NCContentPresenter().messageNotification("E2E delete certificate", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .success)
  258. } else {
  259. NCContentPresenter().messageNotification("E2E delete certificate", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error)
  260. }
  261. }
  262. }
  263. HStack {
  264. Label {
  265. Text("Delete PrivateKey")
  266. } icon: {
  267. Image(systemName: "exclamationmark.triangle")
  268. .resizable()
  269. .scaledToFit()
  270. .font(Font.system(.body).weight(.light))
  271. .foregroundColor(Color(NCBrandColor.shared.textColor2))
  272. }
  273. Spacer()
  274. }
  275. .contentShape(Rectangle())
  276. .onTapGesture {
  277. NextcloudKit.shared.deleteE2EEPrivateKey { _, error in
  278. if error == .success {
  279. NCContentPresenter().messageNotification("E2E delete privateKey", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .success)
  280. } else {
  281. NCContentPresenter().messageNotification("E2E delete privateKey", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error)
  282. }
  283. }
  284. }
  285. }
  286. }
  287. }
  288. // MARK: - Preview / Test
  289. struct SectionView: View {
  290. @State var height: CGFloat = 0
  291. @State var text: String = ""
  292. var body: some View {
  293. HStack {
  294. Text(text)
  295. }
  296. .frame(maxWidth: .infinity, minHeight: height, alignment: .bottomLeading)
  297. }
  298. }
  299. struct NCViewE2EETest: View {
  300. var body: some View {
  301. VStack {
  302. List {
  303. Section(header: SectionView(height: 50, text: "Section Header View")) {
  304. Label {
  305. Text(NSLocalizedString("_e2e_settings_activated_", comment: ""))
  306. } icon: {
  307. Image(systemName: "checkmark.circle.fill")
  308. .resizable()
  309. .scaledToFit()
  310. .frame(width: 25, height: 25)
  311. .font(Font.system(.body).weight(.light))
  312. .foregroundColor(.green)
  313. }
  314. }
  315. Section(header: SectionView(text: "Section Header View 42")) {
  316. Label {
  317. Text(NSLocalizedString("_e2e_settings_activated_", comment: ""))
  318. } icon: {
  319. Image(systemName: "checkmark.circle.fill")
  320. .resizable()
  321. .scaledToFit()
  322. .frame(width: 25, height: 25)
  323. .font(Font.system(.body).weight(.light))
  324. .foregroundColor(.red)
  325. }
  326. }
  327. }
  328. }
  329. }
  330. }
  331. struct NCViewE2EE_Previews: PreviewProvider {
  332. static var previews: some View {
  333. // swiftlint:disable force_cast
  334. let account = (UIApplication.shared.delegate as! AppDelegate).account
  335. NCViewE2EE(account: account, rootViewController: nil)
  336. // swiftlint:enable force_cast
  337. }
  338. }