From 0a57e0648f9ca51008941f2d99f77c7040f022af Mon Sep 17 00:00:00 2001 From: fmodf Date: Sun, 8 Sep 2024 16:57:50 +0200 Subject: [PATCH] wip --- ConversationsClassic/AppData/AppError.swift | 1 + .../AppData/Client/Client.swift | 33 +++++++++++++++---- .../AppData/Services/AESGSMEngine.swift | 18 ++++++++-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/ConversationsClassic/AppData/AppError.swift b/ConversationsClassic/AppData/AppError.swift index 5f5677f..bacb47a 100644 --- a/ConversationsClassic/AppData/AppError.swift +++ b/ConversationsClassic/AppData/AppError.swift @@ -8,4 +8,5 @@ enum AppError: Error { case invalidContentType case invalidLocalName case featureNotSupported + case securityError } diff --git a/ConversationsClassic/AppData/Client/Client.swift b/ConversationsClassic/AppData/Client/Client.swift index 418dce6..40a0331 100644 --- a/ConversationsClassic/AppData/Client/Client.swift +++ b/ConversationsClassic/AppData/Client/Client.swift @@ -109,20 +109,32 @@ extension Client { } func uploadFile(_ localURL: URL) async throws -> String { + // get data from file guard let data = try? Data(contentsOf: localURL) else { throw AppError.noData } - let httpModule = connection.module(HttpFileUploadModule.self) + // encode data with AES_GSM + guard let iv = try? AESGSMEngine.generateIV(), let key = try? AESGSMEngine.generateKey() else { + throw AppError.securityError + } + var encodedData = Data() + var tag = Data() + guard AESGSMEngine.shared.encrypt(iv: iv, key: key, message: data, output: &encodedData, tag: &tag) else { + throw AppError.securityError + } + + // upload + let httpModule = connection.module(HttpFileUploadModule.self) let components = try await httpModule.findHttpUploadComponents() - guard let component = components.first(where: { $0.maxSize > data.count }) else { + guard let component = components.first(where: { $0.maxSize > encodedData.count }) else { throw AppError.fileTooBig } let slot = try await httpModule.requestUploadSlot( componentJid: component.jid, filename: localURL.lastPathComponent, - size: data.count, + size: encodedData.count, contentType: localURL.mimeType ) var request = URLRequest(url: slot.putUri) @@ -130,13 +142,21 @@ extension Client { request.addValue(value, forHTTPHeaderField: key) } request.httpMethod = "PUT" - request.httpBody = data - request.addValue(String(data.count), forHTTPHeaderField: "Content-Length") + request.httpBody = encodedData + request.addValue(String(encodedData.count), forHTTPHeaderField: "Content-Length") request.addValue(localURL.mimeType, forHTTPHeaderField: "Content-Type") let (_, response) = try await URLSession.shared.data(for: request) switch response { case let httpResponse as HTTPURLResponse where httpResponse.statusCode == 201: - return slot.getUri.absoluteString + guard var parts = URLComponents(url: slot.getUri, resolvingAgainstBaseURL: true) else { + throw URLError(.badServerResponse) + } + parts.scheme = "aesgcm" + parts.fragment = (iv + key).map { String(format: "%02x", $0) }.joined() + guard let shareUrl = parts.url else { + throw URLError(.badServerResponse) + } + return shareUrl.absoluteString default: throw URLError(.badServerResponse) @@ -169,6 +189,7 @@ private extension Client { switch error { case .noSession: errorMessage = NSLocalizedString("There is no trusted device to send message to", comment: "message encryption failure") + default: break } diff --git a/ConversationsClassic/AppData/Services/AESGSMEngine.swift b/ConversationsClassic/AppData/Services/AESGSMEngine.swift index 114514f..177a389 100644 --- a/ConversationsClassic/AppData/Services/AESGSMEngine.swift +++ b/ConversationsClassic/AppData/Services/AESGSMEngine.swift @@ -12,8 +12,8 @@ final class AESGSMEngine: AES_GCM_Engine { let symmetricKey = SymmetricKey(data: key) let sealedBox = try AES.GCM.seal(message, using: symmetricKey, nonce: AES.GCM.Nonce(data: iv)) - if let output = output { - output.pointee = sealedBox.ciphertext + if let output = output, let data = sealedBox.combined { + output.pointee = data } if let tag = tag { tag.pointee = sealedBox.tag @@ -44,4 +44,18 @@ final class AESGSMEngine: AES_GCM_Engine { return false } } + + static func generateIV() throws -> Data { + var bytes = [Int8](repeating: 0, count: 12) + let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) + if status != errSecSuccess { + throw AppError.securityError + } + return Data(bytes: bytes, count: bytes.count) + } + + static func generateKey() throws -> Data { + let key = SymmetricKey(size: .bits256) + return key.withUnsafeBytes { Data($0) } + } }