// // NCNetworkingE2EE.swift // Nextcloud // // Created by Marino Faggiana on 05/05/2020. // Copyright © 2020 Marino Faggiana. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // import Foundation import UIKit import NextcloudKit import Alamofire class NCNetworkingE2EE: NSObject { let database = NCManageDatabase.shared let e2EEApiVersion1 = "v1" let e2EEApiVersion2 = "v2" func isInUpload(account: String, serverUrl: String) -> Bool { let counter = self.database.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND (status == %d OR status == %d)", account, serverUrl, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploading)).count return counter > 0 ? true : false } func generateRandomIdentifier() -> String { var UUID = NSUUID().uuidString UUID = "E2EE" + UUID.replacingOccurrences(of: "-", with: "") return UUID } func getOptions(account: String) -> NKRequestOptions { let version = NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 ? e2EEApiVersion2 : e2EEApiVersion1 return NKRequestOptions(version: version) } // MARK: - func getMetadata(fileId: String, e2eToken: String?, account: String, completion: @escaping (_ account: String, _ version: String?, _ e2eMetadata: String?, _ signature: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { switch NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion { case NCGlobal.shared.e2eeVersionV11, NCGlobal.shared.e2eeVersionV12: NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, account: account, options: NKRequestOptions(version: e2EEApiVersion1)) { account, e2eMetadata, signature, data, error in return completion(account, self.e2EEApiVersion1, e2eMetadata, signature, data, error) } case NCGlobal.shared.e2eeVersionV20: var options = NKRequestOptions(version: e2EEApiVersion2) NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, account: account, options: options) { account, e2eMetadata, signature, data, error in if error == .success { return completion(account, self.e2EEApiVersion2, e2eMetadata, signature, data, error) } else if error.errorCode == NCGlobal.shared.errorResourceNotFound { return completion(account, self.e2EEApiVersion2, e2eMetadata, signature, data, error) } else { options = NKRequestOptions(version: self.e2EEApiVersion1) NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, account: account, options: options) { account, e2eMetadata, signature, data, error in completion(account, self.e2EEApiVersion1, e2eMetadata, signature, data, error) } } } default: completion("", "", nil, nil, nil, NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "version e2ee not available")) } } func getMetadata(fileId: String, e2eToken: String?, account: String) async -> (account: String, version: String?, e2eMetadata: String?, signature: String?, responseData: AFDataResponse?, error: NKError) { await withUnsafeContinuation({ continuation in getMetadata(fileId: fileId, e2eToken: e2eToken, account: account) { account, version, e2eMetadata, signature, responseData, error in continuation.resume(returning: (account: account, version: version, e2eMetadata: e2eMetadata, signature: signature, responseData: responseData, error: error)) } }) } // MARK: - func uploadMetadata(serverUrl: String, addUserId: String? = nil, removeUserId: String? = nil, updateVersionV1V2: Bool = false, account: String) async -> NKError { var addCertificate: String? var method = "POST" let session = NCSession.shared.getSession(account: account) guard let directory = self.database.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, serverUrl)) else { return NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_") } if let addUserId { let results = await NCNetworking.shared.getE2EECertificate(user: addUserId, account: session.account, options: NCNetworkingE2EE().getOptions(account: account)) if results.error == .success, let certificateUser = results.certificateUser { addCertificate = certificateUser } else { return results.error } } // LOCK // let resultsLock = await lock(account: session.account, serverUrl: serverUrl) guard resultsLock.error == .success, let e2eToken = resultsLock.e2eToken, let fileId = resultsLock.fileId else { return resultsLock.error } // METHOD // if updateVersionV1V2 { method = "PUT" } else { let resultsGetE2EEMetadata = await getMetadata(fileId: fileId, e2eToken: e2eToken, account: session.account) if resultsGetE2EEMetadata.error == .success { method = "PUT" } else if resultsGetE2EEMetadata.error.errorCode != NCGlobal.shared.errorResourceNotFound { return resultsGetE2EEMetadata.error } } // UPLOAD METADATA // let uploadMetadataError = await uploadMetadata(serverUrl: serverUrl, ocIdServerUrl: directory.ocId, fileId: fileId, e2eToken: e2eToken, method: method, addUserId: addUserId, addCertificate: addCertificate, removeUserId: removeUserId, session: session) guard uploadMetadataError == .success else { await unlock(account: session.account, serverUrl: serverUrl) return uploadMetadataError } // UNLOCK // await unlock(account: session.account, serverUrl: serverUrl) return NKError() } func uploadMetadata(serverUrl: String, ocIdServerUrl: String, fileId: String, e2eToken: String, method: String, addUserId: String? = nil, addCertificate: String? = nil, removeUserId: String? = nil, session: NCSession.Session) async -> NKError { let resultsEncodeMetadata = NCEndToEndMetadata().encodeMetadata(serverUrl: serverUrl, addUserId: addUserId, addCertificate: addCertificate, removeUserId: removeUserId, session: session) guard resultsEncodeMetadata.error == .success, let e2eMetadata = resultsEncodeMetadata.metadata else { // Client Diagnostic self.database.addDiagnostic(account: session.account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) return resultsEncodeMetadata.error } 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)) guard putE2EEMetadataResults.error == .success else { return putE2EEMetadataResults.error } // COUNTER // if NCCapabilities.shared.getCapabilities(account: session.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { self.database.updateCounterE2eMetadata(account: session.account, ocIdServerUrl: ocIdServerUrl, counter: resultsEncodeMetadata.counter) } return NKError() } // MARK: - func downloadMetadata(serverUrl: String, fileId: String, e2eToken: String, session: NCSession.Session) async -> NKError { let resultsGetE2EEMetadata = await getMetadata(fileId: fileId, e2eToken: e2eToken, account: session.account) guard resultsGetE2EEMetadata.error == .success, let e2eMetadata = resultsGetE2EEMetadata.e2eMetadata else { return resultsGetE2EEMetadata.error } let resultsDecodeMetadataError = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: resultsGetE2EEMetadata.signature, serverUrl: serverUrl, session: session) guard resultsDecodeMetadataError == .success else { // Client Diagnostic self.database.addDiagnostic(account: session.account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) return resultsDecodeMetadataError } return NKError() } // MARK: - func lock(account: String, serverUrl: String) async -> (fileId: String?, e2eToken: String?, error: NKError) { var e2eToken: String? var e2eCounter = "1" guard let directory = self.database.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", account, serverUrl)) else { return (nil, nil, NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_")) } if let tableLock = self.database.getE2ETokenLock(account: account, serverUrl: serverUrl) { e2eToken = tableLock.e2eToken } if NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20, var counter = self.database.getCounterE2eMetadata(account: account, ocIdServerUrl: directory.ocId) { counter += 1 e2eCounter = "\(counter)" } let resultsLockE2EEFolder = await NCNetworking.shared.lockE2EEFolder(fileId: directory.fileId, e2eToken: e2eToken, e2eCounter: e2eCounter, method: "POST", account: account, options: NCNetworkingE2EE().getOptions(account: account)) if resultsLockE2EEFolder.error == .success, let e2eToken = resultsLockE2EEFolder.e2eToken { self.database.setE2ETokenLock(account: account, serverUrl: serverUrl, fileId: directory.fileId, e2eToken: e2eToken) } return (directory.fileId, resultsLockE2EEFolder.e2eToken, resultsLockE2EEFolder.error) } func unlock(account: String, serverUrl: String) async { guard let tableLock = self.database.getE2ETokenLock(account: account, serverUrl: serverUrl) else { return } let resultsLockE2EEFolder = await NCNetworking.shared.lockE2EEFolder(fileId: tableLock.fileId, e2eToken: tableLock.e2eToken, e2eCounter: nil, method: "DELETE", account: account, options: NCNetworkingE2EE().getOptions(account: account)) if resultsLockE2EEFolder.error == .success { self.database.deleteE2ETokenLock(account: account, serverUrl: serverUrl) } return } func unlockAll(account: String) { guard NCKeychain().isEndToEndEnabled(account: account) else { return } Task { for result in self.database.getE2EAllTokenLock(account: account) { let resultsLockE2EEFolder = await NCNetworking.shared.lockE2EEFolder(fileId: result.fileId, e2eToken: result.e2eToken, e2eCounter: nil, method: "DELETE", account: account, options: NCNetworkingE2EE().getOptions(account: account)) if resultsLockE2EEFolder.error == .success { self.database.deleteE2ETokenLock(account: account, serverUrl: result.serverUrl) } } } } }