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

324 lines
14 KiB
Swift
Raw Permalink Normal View History

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