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 invalidLocalName
case featureNotSupported
case securityError
}

View file

@ -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
}

View file

@ -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) }
}
}