This commit is contained in:
fmodf 2024-09-08 16:57:50 +02:00
parent b3b3b3aef7
commit 0a57e0648f
3 changed files with 44 additions and 8 deletions

View file

@ -8,4 +8,5 @@ enum AppError: Error {
case invalidContentType case invalidContentType
case invalidLocalName case invalidLocalName
case featureNotSupported case featureNotSupported
case securityError
} }

View file

@ -109,20 +109,32 @@ extension Client {
} }
func uploadFile(_ localURL: URL) async throws -> String { func uploadFile(_ localURL: URL) async throws -> String {
// get data from file
guard let data = try? Data(contentsOf: localURL) else { guard let data = try? Data(contentsOf: localURL) else {
throw AppError.noData 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() 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 throw AppError.fileTooBig
} }
let slot = try await httpModule.requestUploadSlot( let slot = try await httpModule.requestUploadSlot(
componentJid: component.jid, componentJid: component.jid,
filename: localURL.lastPathComponent, filename: localURL.lastPathComponent,
size: data.count, size: encodedData.count,
contentType: localURL.mimeType contentType: localURL.mimeType
) )
var request = URLRequest(url: slot.putUri) var request = URLRequest(url: slot.putUri)
@ -130,13 +142,21 @@ extension Client {
request.addValue(value, forHTTPHeaderField: key) request.addValue(value, forHTTPHeaderField: key)
} }
request.httpMethod = "PUT" request.httpMethod = "PUT"
request.httpBody = data request.httpBody = encodedData
request.addValue(String(data.count), forHTTPHeaderField: "Content-Length") request.addValue(String(encodedData.count), forHTTPHeaderField: "Content-Length")
request.addValue(localURL.mimeType, forHTTPHeaderField: "Content-Type") request.addValue(localURL.mimeType, forHTTPHeaderField: "Content-Type")
let (_, response) = try await URLSession.shared.data(for: request) let (_, response) = try await URLSession.shared.data(for: request)
switch response { switch response {
case let httpResponse as HTTPURLResponse where httpResponse.statusCode == 201: 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: default:
throw URLError(.badServerResponse) throw URLError(.badServerResponse)
@ -169,6 +189,7 @@ private extension Client {
switch error { switch error {
case .noSession: case .noSession:
errorMessage = NSLocalizedString("There is no trusted device to send message to", comment: "message encryption failure") errorMessage = NSLocalizedString("There is no trusted device to send message to", comment: "message encryption failure")
default: default:
break break
} }

View file

@ -12,8 +12,8 @@ final class AESGSMEngine: AES_GCM_Engine {
let symmetricKey = SymmetricKey(data: key) let symmetricKey = SymmetricKey(data: key)
let sealedBox = try AES.GCM.seal(message, using: symmetricKey, nonce: AES.GCM.Nonce(data: iv)) let sealedBox = try AES.GCM.seal(message, using: symmetricKey, nonce: AES.GCM.Nonce(data: iv))
if let output = output { if let output = output, let data = sealedBox.combined {
output.pointee = sealedBox.ciphertext output.pointee = data
} }
if let tag = tag { if let tag = tag {
tag.pointee = sealedBox.tag tag.pointee = sealedBox.tag
@ -44,4 +44,18 @@ final class AESGSMEngine: AES_GCM_Engine {
return false 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) }
}
} }