2024-04-30 09:41:03 +02:00
|
|
|
//
|
2024-06-03 12:05:19 +02:00
|
|
|
// Request.swift
|
2024-04-30 09:41:03 +02:00
|
|
|
// Befund
|
|
|
|
//
|
2024-06-03 12:05:19 +02:00
|
|
|
// Created by Irakli Abetschkhrischwili on 15.05.22.
|
|
|
|
// Copyright © 2022 MVZ Dr. Stein und Kollegen. All rights reserved.
|
2024-04-30 09:41:03 +02:00
|
|
|
|
|
|
|
import Foundation
|
2024-06-03 12:05:19 +02:00
|
|
|
import UIKit
|
|
|
|
import Combine
|
|
|
|
import CryptoKit
|
|
|
|
|
|
|
|
|
2024-06-04 10:46:10 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-03 12:05:19 +02:00
|
|
|
extension Core
|
|
|
|
{
|
2024-06-04 10:46:10 +02:00
|
|
|
|
2024-06-03 12:05:19 +02:00
|
|
|
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)
|
|
|
|
{
|
2024-06-04 10:46:10 +02:00
|
|
|
_publicKey = try await postRequest.getPublicKey(host: host, _keyPair: keyPair)
|
2024-06-03 12:05:19 +02:00
|
|
|
}
|
|
|
|
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 {
|
2024-06-04 10:46:10 +02:00
|
|
|
let _shared = try await sharedKey
|
|
|
|
if(_shared != nil)
|
|
|
|
{
|
|
|
|
let encryptedRequest = Core.Models.Request.EncryptedRequest(descriptor: descriptor, contentObject: contentObject, requestType: requestType, key: _shared!)
|
2024-06-03 12:05:19 +02:00
|
|
|
|
2024-06-04 10:46:10 +02:00
|
|
|
let encryptedResponse = try await postRequest.getEncryptedResponse(host: host, controller: controller, action: action, _request: encryptedRequest, serverPublicKey: publicKey, _keyPair: keyPair)
|
|
|
|
|
|
|
|
return encryptedResponse
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return nil
|
|
|
|
}
|
2024-06-03 12:05:19 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2024-06-04 10:46:10 +02:00
|
|
|
var (data,response) : (Data, URLResponse)
|
|
|
|
let keyPair = PrepareKeyPair(_keyPair: _keyPair)
|
2024-06-03 12:05:19 +02:00
|
|
|
do
|
2024-06-04 10:46:10 +02:00
|
|
|
{
|
2024-06-03 12:05:19 +02:00
|
|
|
let request = try PrepareURLRequest(host: host, controller: controller, action: action, _request: _request, keyPair: keyPair)
|
|
|
|
|
2024-06-04 10:46:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2024-06-03 12:05:19 +02:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-04 10:46:10 +02:00
|
|
|
else
|
2024-06-03 12:05:19 +02:00
|
|
|
{
|
2024-06-04 10:46:10 +02:00
|
|
|
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
|
2024-06-03 12:05:19 +02:00
|
|
|
}
|
2024-06-04 10:46:10 +02:00
|
|
|
|
2024-06-03 12:05:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@MainActor
|
2024-06-04 10:46:10 +02:00
|
|
|
func getPublicKey(host: Https.Servers, _keyPair: Core.Security.Curve25519.KeyPair? = nil) async throws -> Core.Models.Response.PublicKey
|
2024-06-03 12:05:19 +02:00
|
|
|
{
|
2024-06-04 10:46:10 +02:00
|
|
|
var (data,response) : (Data, URLResponse)
|
2024-06-03 12:05:19 +02:00
|
|
|
do
|
|
|
|
{
|
|
|
|
let keyPair = PrepareKeyPair(_keyPair: _keyPair)
|
|
|
|
|
2024-06-04 10:46:10 +02:00
|
|
|
let request = try PrepareURLRequest(host: host, controller: "exchange", action: "key", _request: nil, keyPair: keyPair)
|
2024-06-03 12:05:19 +02:00
|
|
|
|
|
|
|
let s = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
2024-06-04 10:46:10 +02:00
|
|
|
(data,response) = try await s.data(for: request)
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription)
|
|
|
|
}
|
|
|
|
|
|
|
|
let httpR = response as? HTTPURLResponse
|
|
|
|
if(httpR != nil)
|
|
|
|
{
|
2024-06-03 12:05:19 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-04 10:46:10 +02:00
|
|
|
else
|
2024-06-03 12:05:19 +02:00
|
|
|
{
|
2024-06-04 10:46:10 +02:00
|
|
|
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
|
2024-06-03 12:05:19 +02:00
|
|
|
}
|
2024-06-04 10:46:10 +02:00
|
|
|
|
2024-06-03 12:05:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
nonisolated public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
2024-06-04 10:46:10 +02:00
|
|
|
#if DEBUG
|
|
|
|
// Accept all certificates
|
|
|
|
let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
|
|
|
|
completionHandler(.useCredential, urlCredential)
|
|
|
|
#else
|
|
|
|
completionHandler(.performDefaultHandling, nil)
|
|
|
|
#endif
|
2024-06-03 12:05:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|