NCNetworkingE2EE.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. //
  2. // NCNetworkingE2EE.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 05/05/2020.
  6. // Copyright © 2020 Marino Faggiana. All rights reserved.
  7. //
  8. // This program is free software: you can redistribute it and/or modify
  9. // it under the terms of the GNU General Public License as published by
  10. // the Free Software Foundation, either version 3 of the License, or
  11. // (at your option) any later version.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU General Public License
  19. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. //
  21. import Foundation
  22. import UIKit
  23. import NextcloudKit
  24. import Alamofire
  25. class NCNetworkingE2EE: NSObject {
  26. let database = NCManageDatabase.shared
  27. let e2EEApiVersion1 = "v1"
  28. let e2EEApiVersion2 = "v2"
  29. func isInUpload(account: String, serverUrl: String) -> Bool {
  30. let counter = self.database.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND (status == %d OR status == %d)",
  31. account,
  32. serverUrl,
  33. NCGlobal.shared.metadataStatusWaitUpload,
  34. NCGlobal.shared.metadataStatusUploading)).count
  35. return counter > 0 ? true : false
  36. }
  37. func generateRandomIdentifier() -> String {
  38. var UUID = NSUUID().uuidString
  39. UUID = "E2EE" + UUID.replacingOccurrences(of: "-", with: "")
  40. return UUID
  41. }
  42. func getOptions(account: String) -> NKRequestOptions {
  43. let version = NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 ? e2EEApiVersion2 : e2EEApiVersion1
  44. return NKRequestOptions(version: version)
  45. }
  46. // MARK: -
  47. func getMetadata(fileId: String,
  48. e2eToken: String?,
  49. account: String,
  50. completion: @escaping (_ account: String, _ version: String?, _ e2eMetadata: String?, _ signature: String?, _ responseData: AFDataResponse<Data>?, _ error: NKError) -> Void) {
  51. switch NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion {
  52. case NCGlobal.shared.e2eeVersionV11, NCGlobal.shared.e2eeVersionV12:
  53. NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, account: account, options: NKRequestOptions(version: e2EEApiVersion1)) { account, e2eMetadata, signature, data, error in
  54. return completion(account, self.e2EEApiVersion1, e2eMetadata, signature, data, error)
  55. }
  56. case NCGlobal.shared.e2eeVersionV20:
  57. var options = NKRequestOptions(version: e2EEApiVersion2)
  58. NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, account: account, options: options) { account, e2eMetadata, signature, data, error in
  59. if error == .success {
  60. return completion(account, self.e2EEApiVersion2, e2eMetadata, signature, data, error)
  61. } else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
  62. return completion(account, self.e2EEApiVersion2, e2eMetadata, signature, data, error)
  63. } else {
  64. options = NKRequestOptions(version: self.e2EEApiVersion1)
  65. NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, account: account, options: options) { account, e2eMetadata, signature, data, error in
  66. completion(account, self.e2EEApiVersion1, e2eMetadata, signature, data, error)
  67. }
  68. }
  69. }
  70. default:
  71. completion("", "", nil, nil, nil, NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "version e2ee not available"))
  72. }
  73. }
  74. func getMetadata(fileId: String,
  75. e2eToken: String?,
  76. account: String) async -> (account: String, version: String?, e2eMetadata: String?, signature: String?, responseData: AFDataResponse<Data>?, error: NKError) {
  77. await withUnsafeContinuation({ continuation in
  78. getMetadata(fileId: fileId, e2eToken: e2eToken, account: account) { account, version, e2eMetadata, signature, responseData, error in
  79. continuation.resume(returning: (account: account, version: version, e2eMetadata: e2eMetadata, signature: signature, responseData: responseData, error: error))
  80. }
  81. })
  82. }
  83. // MARK: -
  84. func uploadMetadata(serverUrl: String,
  85. addUserId: String? = nil,
  86. removeUserId: String? = nil,
  87. updateVersionV1V2: Bool = false,
  88. account: String) async -> NKError {
  89. var addCertificate: String?
  90. var method = "POST"
  91. let session = NCSession.shared.getSession(account: account)
  92. guard let directory = self.database.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, serverUrl)) else {
  93. return NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_")
  94. }
  95. if let addUserId {
  96. let results = await NCNetworking.shared.getE2EECertificate(user: addUserId, account: session.account, options: NCNetworkingE2EE().getOptions(account: account))
  97. if results.error == .success, let certificateUser = results.certificateUser {
  98. addCertificate = certificateUser
  99. } else {
  100. return results.error
  101. }
  102. }
  103. // LOCK
  104. //
  105. let resultsLock = await lock(account: session.account, serverUrl: serverUrl)
  106. guard resultsLock.error == .success, let e2eToken = resultsLock.e2eToken, let fileId = resultsLock.fileId else {
  107. return resultsLock.error
  108. }
  109. // METHOD
  110. //
  111. if updateVersionV1V2 {
  112. method = "PUT"
  113. } else {
  114. let resultsGetE2EEMetadata = await getMetadata(fileId: fileId, e2eToken: e2eToken, account: session.account)
  115. if resultsGetE2EEMetadata.error == .success {
  116. method = "PUT"
  117. } else if resultsGetE2EEMetadata.error.errorCode != NCGlobal.shared.errorResourceNotFound {
  118. return resultsGetE2EEMetadata.error
  119. }
  120. }
  121. // UPLOAD METADATA
  122. //
  123. let uploadMetadataError = await uploadMetadata(serverUrl: serverUrl,
  124. ocIdServerUrl: directory.ocId,
  125. fileId: fileId,
  126. e2eToken: e2eToken,
  127. method: method,
  128. addUserId: addUserId,
  129. addCertificate: addCertificate,
  130. removeUserId: removeUserId,
  131. session: session)
  132. guard uploadMetadataError == .success else {
  133. await unlock(account: session.account, serverUrl: serverUrl)
  134. return uploadMetadataError
  135. }
  136. // UNLOCK
  137. //
  138. await unlock(account: session.account, serverUrl: serverUrl)
  139. return NKError()
  140. }
  141. func uploadMetadata(serverUrl: String,
  142. ocIdServerUrl: String,
  143. fileId: String,
  144. e2eToken: String,
  145. method: String,
  146. addUserId: String? = nil,
  147. addCertificate: String? = nil,
  148. removeUserId: String? = nil,
  149. session: NCSession.Session) async -> NKError {
  150. let resultsEncodeMetadata = NCEndToEndMetadata().encodeMetadata(serverUrl: serverUrl, addUserId: addUserId, addCertificate: addCertificate, removeUserId: removeUserId, session: session)
  151. guard resultsEncodeMetadata.error == .success,
  152. let e2eMetadata = resultsEncodeMetadata.metadata else {
  153. // Client Diagnostic
  154. self.database.addDiagnostic(account: session.account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
  155. return resultsEncodeMetadata.error
  156. }
  157. let putE2EEMetadataResults = await NCNetworking.shared.putE2EEMetadata(fileId: fileId, e2eToken: e2eToken, e2eMetadata: e2eMetadata, signature: resultsEncodeMetadata.signature, method: method, account: session.account, options: NCNetworkingE2EE().getOptions(account: session.account))
  158. guard putE2EEMetadataResults.error == .success else {
  159. return putE2EEMetadataResults.error
  160. }
  161. // COUNTER
  162. //
  163. if NCCapabilities.shared.getCapabilities(account: session.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
  164. self.database.updateCounterE2eMetadata(account: session.account, ocIdServerUrl: ocIdServerUrl, counter: resultsEncodeMetadata.counter)
  165. }
  166. return NKError()
  167. }
  168. // MARK: -
  169. func downloadMetadata(serverUrl: String,
  170. fileId: String,
  171. e2eToken: String,
  172. session: NCSession.Session) async -> NKError {
  173. let resultsGetE2EEMetadata = await getMetadata(fileId: fileId, e2eToken: e2eToken, account: session.account)
  174. guard resultsGetE2EEMetadata.error == .success, let e2eMetadata = resultsGetE2EEMetadata.e2eMetadata else {
  175. return resultsGetE2EEMetadata.error
  176. }
  177. let resultsDecodeMetadataError = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: resultsGetE2EEMetadata.signature, serverUrl: serverUrl, session: session)
  178. guard resultsDecodeMetadataError == .success else {
  179. // Client Diagnostic
  180. self.database.addDiagnostic(account: session.account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
  181. return resultsDecodeMetadataError
  182. }
  183. return NKError()
  184. }
  185. // MARK: -
  186. func lock(account: String,
  187. serverUrl: String) async -> (fileId: String?, e2eToken: String?, error: NKError) {
  188. var e2eToken: String?
  189. var e2eCounter = "1"
  190. guard let directory = self.database.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", account, serverUrl)) else {
  191. return (nil, nil, NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_"))
  192. }
  193. if let tableLock = self.database.getE2ETokenLock(account: account, serverUrl: serverUrl) {
  194. e2eToken = tableLock.e2eToken
  195. }
  196. if NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20,
  197. var counter = self.database.getCounterE2eMetadata(account: account, ocIdServerUrl: directory.ocId) {
  198. counter += 1
  199. e2eCounter = "\(counter)"
  200. }
  201. let resultsLockE2EEFolder = await NCNetworking.shared.lockE2EEFolder(fileId: directory.fileId, e2eToken: e2eToken, e2eCounter: e2eCounter, method: "POST", account: account, options: NCNetworkingE2EE().getOptions(account: account))
  202. if resultsLockE2EEFolder.error == .success, let e2eToken = resultsLockE2EEFolder.e2eToken {
  203. self.database.setE2ETokenLock(account: account, serverUrl: serverUrl, fileId: directory.fileId, e2eToken: e2eToken)
  204. }
  205. return (directory.fileId, resultsLockE2EEFolder.e2eToken, resultsLockE2EEFolder.error)
  206. }
  207. func unlock(account: String, serverUrl: String) async {
  208. guard let tableLock = self.database.getE2ETokenLock(account: account, serverUrl: serverUrl) else {
  209. return
  210. }
  211. let resultsLockE2EEFolder = await NCNetworking.shared.lockE2EEFolder(fileId: tableLock.fileId, e2eToken: tableLock.e2eToken, e2eCounter: nil, method: "DELETE", account: account, options: NCNetworkingE2EE().getOptions(account: account))
  212. if resultsLockE2EEFolder.error == .success {
  213. self.database.deleteE2ETokenLock(account: account, serverUrl: serverUrl)
  214. }
  215. return
  216. }
  217. func unlockAll(account: String) {
  218. guard NCKeychain().isEndToEndEnabled(account: account) else { return }
  219. Task {
  220. for result in self.database.getE2EAllTokenLock(account: account) {
  221. let resultsLockE2EEFolder = await NCNetworking.shared.lockE2EEFolder(fileId: result.fileId, e2eToken: result.e2eToken, e2eCounter: nil, method: "DELETE", account: account, options: NCNetworkingE2EE().getOptions(account: account))
  222. if resultsLockE2EEFolder.error == .success {
  223. self.database.deleteE2ETokenLock(account: account, serverUrl: result.serverUrl)
  224. }
  225. }
  226. }
  227. }
  228. }