Sesame-iOS/Sesame/Common/Client.swift

103 lines
4.3 KiB
Swift
Raw Normal View History

2022-01-29 18:59:42 +01:00
import Foundation
import CryptoKit
final class Client {
2022-01-29 18:59:42 +01:00
2023-12-12 17:33:42 +01:00
private let localRequestRoute = "message"
private let urlMessageParameter = "m"
2022-01-29 18:59:42 +01:00
init() {}
2023-12-12 17:33:42 +01:00
func send(_ message: Message, to url: String, through route: TransmissionType, using keys: KeySet) async -> ServerResponse {
let sentTime = Date.now
let signedMessage = message.authenticate(using: keys.remote)
let response: Message
switch route {
case .throughServer:
response = await send(signedMessage, toServerUrl: url, authenticateWith: keys.server, verifyUsing: keys.device)
case .overLocalWifi:
response = await send(signedMessage, toLocalDeviceUrl: url, verifyUsing: keys.device)
}
let receivedTime = Date.now
// Create best guess for creation of challenge.
let roundTripTime = receivedTime.timeIntervalSince(sentTime)
let serverChallenge = ServerChallenge(
creationDate: sentTime.addingTimeInterval(roundTripTime / 2),
message: response)
// Validate message content
guard response.result == .messageAccepted else {
print("Failure: \(response)")
return (response, nil)
}
guard response.clientChallenge == message.clientChallenge else {
print("Invalid client challenge: \(response)")
return (response.with(result: .invalidClientChallengeFromDevice), nil)
}
return (response, serverChallenge)
}
2023-12-12 17:33:42 +01:00
2023-12-12 17:33:42 +01:00
private func send(_ message: SignedMessage, toLocalDeviceUrl server: String, verifyUsing deviceKey: SymmetricKey) async -> Message {
let data = message.encoded.hexEncoded
2023-12-12 17:33:42 +01:00
guard let url = URL(string: server)?.appendingPathComponent("\(localRequestRoute)?\(urlMessageParameter)=\(data)") else {
return message.message.with(result: .serverUrlInvalid)
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
2023-12-12 17:33:42 +01:00
request.timeoutInterval = 10
return await perform(request, inResponseTo: message.message, verifyUsing: deviceKey)
}
2023-12-12 17:33:42 +01:00
private func send(_ message: SignedMessage, toServerUrl server: String, authenticateWith authToken: Data, verifyUsing deviceKey: SymmetricKey) async -> Message {
guard let url = URL(string: server)?.appendingPathComponent(SesameRoute.postMessage.rawValue) else {
return message.message.with(result: .serverUrlInvalid)
}
2023-12-12 17:33:42 +01:00
2022-01-29 18:59:42 +01:00
var request = URLRequest(url: url)
2023-12-12 17:33:42 +01:00
request.httpBody = message.encoded
2022-01-29 18:59:42 +01:00
request.httpMethod = "POST"
2023-08-09 16:27:34 +02:00
request.timeoutInterval = 10
2023-12-12 17:33:42 +01:00
request.addValue(authToken.hexEncoded, forHTTPHeaderField: SesameHeader.authenticationHeader)
return await perform(request, inResponseTo: message.message, verifyUsing: deviceKey)
}
2023-12-12 17:33:42 +01:00
private func perform(_ request: URLRequest, inResponseTo message: Message, verifyUsing deviceKey: SymmetricKey) async -> Message {
let (response, responseData) = await fulfill(request)
guard response == .messageAccepted, let data = responseData else {
return message.with(result: response)
2022-04-09 17:43:33 +02:00
}
2023-12-12 17:33:42 +01:00
guard data.count == SignedMessage.size else {
print("[WARN] Received message with \(data.count) bytes (\(Array(data)))")
return message.with(result: .invalidMessageSizeFromDevice)
2022-04-09 17:43:33 +02:00
}
2023-12-12 17:33:42 +01:00
let decodedMessage: SignedMessage
do {
decodedMessage = try SignedMessage(decodeFrom: data)
} catch {
return message.with(result: error as! MessageResult)
2022-01-29 18:59:42 +01:00
}
2023-12-12 17:33:42 +01:00
guard decodedMessage.isValid(using: deviceKey) else {
return message.with(result: .invalidSignatureFromDevice)
2022-04-09 17:43:33 +02:00
}
2023-12-12 17:33:42 +01:00
return decodedMessage.message
2022-01-29 18:59:42 +01:00
}
2023-12-12 17:33:42 +01:00
private func fulfill(_ request: URLRequest) async -> (response: MessageResult, data: Data?) {
2022-01-29 18:59:42 +01:00
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let code = (response as? HTTPURLResponse)?.statusCode else {
2023-12-12 17:33:42 +01:00
return (.unexpectedUrlResponseType, nil)
2022-01-29 18:59:42 +01:00
}
2023-12-12 17:33:42 +01:00
return (.init(httpCode: code), data)
2022-01-29 18:59:42 +01:00
} catch {
print("Request failed: \(error)")
2023-12-12 17:33:42 +01:00
return (.deviceTimedOut, nil)
2022-01-29 18:59:42 +01:00
}
}
}