patbef-iOS/Befund/Core/Https/RequestAsync.swift

288 lines
13 KiB
Swift

//
// Request.swift
// Befund
//
// Created by Irakli Abetschkhrischwili on 15.05.22.
// Copyright © 2022 MVZ Dr. Stein und Kollegen. All rights reserved.
import Foundation
import UIKit
import Combine
import CryptoKit
extension Core
{
enum RequestError: Error
{
case HANDLED_ERROR(msg: String)
case UNEXPECTED_ERROR(msg: String)
}
@MainActor
public class EncryptedResponse
{
var settings: Core.Models.Settings
var descriptor: String
var contentObject: Any
var controller: String
var action: String
var requestType: Core.Models.Request.EncryptedRequest.Types
private var _publicKey: Core.Models.Response.PublicKey? = nil
private var _keyPair: Core.Security.Curve25519.KeyPair? = nil
public init(settings: Core.Models.Settings, descriptor: String, contentObject: Any, controller: String, action: String, requestType: Core.Models.Request.EncryptedRequest.Types) {
self.settings = settings
self.descriptor = descriptor
self.contentObject = contentObject
self.controller = controller
self.action = action
self.requestType = requestType
}
private var host: Core.Https.Servers{
get {
return settings.labor?.host ?? .DEVELOPMENT
}
}
private var keyPair: Core.Security.Curve25519.KeyPair {
get {
if(_keyPair == nil){
_keyPair = Core.Security.Curve25519.GenerateKeyPair()
}
return _keyPair!
}
}
private var postRequest: HttpsAsync.Request.PostEncryptedRequestAsync{
get {
return HttpsAsync.Request.PostEncryptedRequestAsync()
}
}
private var publicKey: Core.Models.Response.PublicKey? {
get async throws {
if (_publicKey == nil)
{
_publicKey = try await postRequest.getPublicKey(host: host, controller: controller, action: action, _keyPair: keyPair)
}
return _publicKey
}
}
public var sharedKey: CryptoKit.SymmetricKey? {
get async throws{
return try await keyPair.GetSharedKey(peerPublicKeyBase64: publicKey?.key)
}
}
public var response: Core.Models.Response.EncryptedResponse? {
get async throws {
let encryptedRequest = Core.Models.Request.EncryptedRequest(descriptor: descriptor, contentObject: contentObject, requestType: requestType, key: try await sharedKey!)
let encryptedResponse = try await postRequest.getEncryptedResponse(host: host, controller: controller, action: action, _request: encryptedRequest, serverPublicKey: publicKey, _keyPair: keyPair)
return encryptedResponse
}
}
}
public struct HttpsAsync
{
public class Request
{
/**
* Low Level Header protector
*/
private static let HeaderSecureHash: String = "3HzKQw/&>*VH^?cAR{Qd5pP~6w(V5%aZ";
private static let HeaderValueSecureHash: String = "7*R3P!f:sC4Q8XS:@*J/z:4/sS;V3GnM";
/**
* Sends EncryptedRequest to the server in async mode and calls callback(onSucces or onError)
*/
@MainActor
public class PostEncryptedRequestAsync: NSObject, URLSessionDelegate
{
/**
* Runs encrypted request async and calls ether onSuccess or onError callback
*
* @param host - host of the server s. Servers enum
* @param controller - controller of request
* @param action - action of request
* @param _request - encrypted request that contains descriptor, request object and hmac
* @param serverPublicKey - Exchanged server public key from the first request
* @param _keyPair - Curve25519 KeyPair (Private & Public Keys)
*/
func getEncryptedResponse(host: Https.Servers, controller: String, action: String, _request: Core.Models.Request.EncryptedRequest, serverPublicKey: Core.Models.Response.PublicKey?, _keyPair: Core.Security.Curve25519.KeyPair? = nil) async throws -> Core.Models.Response.EncryptedResponse
{
do
{
let keyPair = PrepareKeyPair(_keyPair: _keyPair)
let request = try PrepareURLRequest(host: host, controller: controller, action: action, _request: _request, keyPair: keyPair)
let (data,response) = try await URLSession.shared.data(for: request)
let httpR = response as? HTTPURLResponse
if (httpR != nil)
{
if (httpR?.statusCode == 429)
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_TOO_MANY_REQUESTS"))
}
else if (httpR?.statusCode == 503)
{
AppDelegate.Session.Maintenance = true
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "MSG_MAINTENANCE"))
}
else
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
if(!Core.Https.Request.validServerHeaders(server: httpR!))
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
else
{
let encrR = try JSONDecoder().decode(Core.Models.Response.EncryptedResponse.self, from: data)
let symKey = keyPair.GetSharedKey(peerPublicKeyBase64: serverPublicKey?.key!)
if(Core.Https.Request.validServerSignature(server: httpR!, encryptedResponse: encrR, deriveKey: symKey!))
{
return encrR
}
else
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
}
}
}
else
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
}
catch
{
throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription)
}
}
@MainActor
func getPublicKey(host: Https.Servers, controller: String, action: String, _keyPair: Core.Security.Curve25519.KeyPair? = nil) async throws -> Core.Models.Response.PublicKey
{
do
{
let keyPair = PrepareKeyPair(_keyPair: _keyPair)
let request = try PrepareURLRequest(host: host, controller: controller, action: action, _request: nil, keyPair: keyPair)
let s = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
let (data,response) = try await s.data(for: request)
let httpR = response as? HTTPURLResponse
if(httpR != nil)
{
if (httpR!.statusCode == 429)
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_TOO_MANY_REQUESTS"))
}
else if (httpR!.statusCode == 503)
{
AppDelegate.Session.Maintenance = true
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "MSG_MAINTENANCE"))
}
else
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
if(!Core.Https.Request.validServerHeaders(server: httpR!))
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
else
{
let publicKey = try JSONDecoder().decode(Core.Models.Response.PublicKey.self, from: data)
return publicKey
}
}
}
else
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
}
catch
{
throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription)
}
}
nonisolated public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let trust = challenge.protectionSpace.serverTrust!
completionHandler(.useCredential, URLCredential(trust:trust))
return
}
completionHandler(.cancelAuthenticationChallenge, nil)
}
private func PrepareKeyPair(_keyPair: Core.Security.Curve25519.KeyPair? = nil) -> Core.Security.Curve25519.KeyPair
{
var keyPair = _keyPair;
if(keyPair == nil)
{
keyPair = Security.Curve25519.GenerateKeyPair()
}
return keyPair!
}
private func PrepareURLRequest(host: Https.Servers, controller: String, action: String, _request: Core.Models.Request.EncryptedRequest?, keyPair: Core.Security.Curve25519.KeyPair) throws -> URLRequest
{
let strURL = Core.Https.Request.wellFormedControllerAction(host: host, controller: controller, action: action)
let url = URL(string: strURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(host.rawValue, forHTTPHeaderField: "Host")
request.setValue("BefundApp", forHTTPHeaderField: "User-Agent")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(Security.AES.Encrypt(value: HeaderValueSecureHash, deriveKey: SymmetricKey(data: Security.AES.GetKey(password: HeaderSecureHash))), forHTTPHeaderField: "Client-Hash")
request.setValue(keyPair.PublicKey, forHTTPHeaderField: "Client-Key")
if(_request == nil)
{
request.httpBody = try JSONEncoder().encode(Core.Models.Request.KeyExchange())
}
else
{
request.setValue(keyPair.SigningPublicKey, forHTTPHeaderField: "Client-Signature-Key")
request.setValue(keyPair.GetSignature(signingValue: (_request!.encrypted_content != nil ? _request!.encrypted_content : _request!.descriptor)!), forHTTPHeaderField: "Client-Signature")
request.httpBody = try JSONEncoder().encode(_request)
}
return request
}
}
}
}
}