// // 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 SecTrust { var isSelfSigned: Bool? { guard SecTrustGetCertificateCount(self) == 1 else { return false } guard let cert = SecTrustGetCertificateAtIndex(self, 0) else { return nil } return cert.isSelfSigned } } extension SecCertificate { var isSelfSigned: Bool? { guard let subject = SecCertificateCopyNormalizedSubjectSequence(self), let issuer = SecCertificateCopyNormalizedIssuerSequence(self) else { return nil } return subject == issuer } } 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, _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 _shared = try await sharedKey if(_shared != nil) { let encryptedRequest = Core.Models.Request.EncryptedRequest(descriptor: descriptor, contentObject: contentObject, requestType: requestType, key: _shared!) let encryptedResponse = try await postRequest.getEncryptedResponse(host: host, controller: controller, action: action, _request: encryptedRequest, serverPublicKey: publicKey, _keyPair: keyPair) return encryptedResponse } else { return nil } } } } 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 { var (data,response) : (Data, URLResponse) let keyPair = PrepareKeyPair(_keyPair: _keyPair) do { let request = try PrepareURLRequest(host: host, controller: controller, action: action, _request: _request, keyPair: keyPair) let s = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil) (data,response) = try await s.data(for: request) } catch { throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription) } 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")) } } @MainActor func getPublicKey(host: Https.Servers, _keyPair: Core.Security.Curve25519.KeyPair? = nil) async throws -> Core.Models.Response.PublicKey { var (data,response) : (Data, URLResponse) do { let keyPair = PrepareKeyPair(_keyPair: _keyPair) let request = try PrepareURLRequest(host: host, controller: "exchange", action: "key", _request: nil, keyPair: keyPair) let s = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil) (data,response) = try await s.data(for: request) } catch { throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription) } 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")) } } nonisolated public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { #if DEBUG // Accept all certificates let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, urlCredential) #else completionHandler(.performDefaultHandling, nil) #endif } 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 } } } } }