2024-01-29 16:20:42 +01:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
{
|
|
|
|
public struct Https
|
|
|
|
{
|
|
|
|
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 Key Exchange Request to the server in async mode and calls callback(onSucces or onError)
|
|
|
|
* @param host - Servers.Host that should get this request
|
|
|
|
* @param keyPair - Client keyPair(if not given it will create a new one)
|
|
|
|
* @param onSuccess - onSuccess Handler
|
|
|
|
* @param onError - onError Handler
|
|
|
|
*/
|
|
|
|
static func KeyExchangeAsync(host: Servers, keyPair: Core.Security.Curve25519.KeyPair? = nil, onSuccess: ((Core.Models.Response.PublicKey)->Void)? = nil, onError: ((String)->Void)? = nil)
|
|
|
|
{
|
|
|
|
PostKeyExchangeRequestAsync().RunAsync(host: host, controller: "exchange", action: "key", _keyPair: keyPair, onSuccess: onSuccess, onError: onError);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends Encrypted Request to the server in async mode and calls callback(onSucces or onError)
|
|
|
|
* @param host - Servers.Host that should get this request
|
|
|
|
* @param controller - controller of the request
|
|
|
|
* @param action - controller action of the request
|
|
|
|
* @param request - Encrypted Request that should be sent to the server
|
|
|
|
* @param serverPublicKey - Server Public Key from Exchange Request
|
|
|
|
* @param keyPair - Client KeyPair (if not given it will create a new one)
|
|
|
|
* @param onSuccess - onSuccess Handler
|
|
|
|
* @param onError - onError Handler
|
|
|
|
*/
|
|
|
|
public static func EncryptedRequestAsync(host: Servers, controller: String, action: String, request: Core.Models.Request.EncryptedRequest, serverPublicKey: Core.Models.Response.PublicKey, keyPair: Core.Security.Curve25519.KeyPair? = nil, onSuccess: ((Core.Models.Response.EncryptedResponse)->Void)? = nil, onError: ((String)->Void)? = nil)
|
|
|
|
{
|
|
|
|
PostEncryptedRequestAsync().RunAsync(host: host, controller: controller, action: action, _request: request, serverPublicKey:serverPublicKey, _keyPair: keyPair, onSuccess: onSuccess, onError: onError);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends EncryptedRequest to the server in async mode and calls callback(onSucces or onError)
|
|
|
|
*/
|
|
|
|
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)
|
|
|
|
* @param onSuccess - onSuccess callback that contains public key of response(Server)
|
|
|
|
* @param onError - onError callback that contains error message
|
|
|
|
*/
|
|
|
|
func RunAsync(host: Servers, controller: String, action: String, _request: Core.Models.Request.EncryptedRequest, serverPublicKey: Core.Models.Response.PublicKey, _keyPair: Core.Security.Curve25519.KeyPair? = nil, onSuccess: ((Core.Models.Response.EncryptedResponse)->Void)? = nil, onError: ((String)->Void)? = nil)
|
|
|
|
{
|
|
|
|
var keyPair = _keyPair;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if(keyPair == nil)
|
|
|
|
{
|
|
|
|
keyPair = Security.Curve25519.GenerateKeyPair()
|
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
|
|
|
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)
|
|
|
|
|
|
|
|
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
|
|
|
|
|
|
|
DispatchQueue.global(qos: .background).async
|
|
|
|
{
|
|
|
|
let task = session.dataTask(with: request)
|
|
|
|
{
|
|
|
|
if($2 != nil)
|
|
|
|
{
|
|
|
|
self.responseError(message: $2!.localizedDescription, callback: onError)
|
|
|
|
Log.Error(err: $2!, namespace: "Core.Https.Request.PostEncryptedRequestAsync", method: "RunAsync")
|
|
|
|
}
|
|
|
|
|
|
|
|
if($1 != nil)
|
|
|
|
{
|
|
|
|
let httpUrlResponse = $1 as? HTTPURLResponse
|
|
|
|
|
|
|
|
if(httpUrlResponse != nil)
|
|
|
|
{
|
|
|
|
if(httpUrlResponse!.statusCode == 429)
|
|
|
|
{
|
|
|
|
AppDelegate.Session.Maintenance = false
|
|
|
|
AppDelegate.Session.MaintenanceNotified = false
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "ERROR_TOO_MANY_REQUESTS"), callback: onError)
|
|
|
|
}
|
|
|
|
else if (httpUrlResponse!.statusCode == 503)
|
|
|
|
{
|
|
|
|
AppDelegate.Session.Maintenance = true
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "MSG_MAINTENANCE"), callback: onError)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
AppDelegate.Session.Maintenance = false
|
|
|
|
AppDelegate.Session.MaintenanceNotified = false
|
|
|
|
|
|
|
|
if(!Core.Https.Request.validServerHeaders(server: httpUrlResponse!))
|
|
|
|
{
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"), callback: onError)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if($0 != nil)
|
|
|
|
{
|
|
|
|
self.responseEncryptedResponse(data: $0!, server: httpUrlResponse!, deriveKey: keyPair!.GetSharedKey(peerPublicKeyBase64: serverPublicKey.key!)!, callback: onSuccess)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"), callback: onError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
task.resume()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
Log.Error(err: error, namespace: "Core.Https.Request.PostKeyExchangeRequestAsync", method: "RunAsync")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls OnError Handler with a error message
|
|
|
|
*
|
|
|
|
* @param message - error message
|
|
|
|
* @callback - callback of error response
|
|
|
|
*/
|
|
|
|
private func responseError(message: String, callback: ((String)->Void)? = nil)
|
|
|
|
{
|
|
|
|
if(callback != nil)
|
|
|
|
{
|
|
|
|
callback!(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls OnSuccess Handler with a EncryptedResponse
|
|
|
|
* @param data - response data
|
|
|
|
* @param server - server context that contains response header
|
|
|
|
* @param deriveKey - deriveKey
|
|
|
|
* @param callback - onSuccess callback
|
|
|
|
*/
|
|
|
|
private func responseEncryptedResponse(data: Data, server: HTTPURLResponse, deriveKey: SymmetricKey, callback: ((Core.Models.Response.EncryptedResponse)->Void)? = nil)
|
|
|
|
{
|
|
|
|
if(callback != nil)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
let encryptedResponse = try JSONDecoder().decode(Core.Models.Response.EncryptedResponse.self, from: data)
|
|
|
|
|
|
|
|
if(Core.Https.Request.validServerSignature(server: server, encryptedResponse: encryptedResponse, deriveKey: deriveKey))
|
|
|
|
{
|
|
|
|
callback!(encryptedResponse)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
Log.Error(err: error, namespace: "Core.Https.Request.PostKeyExchangeRequestAsync", method: "responseEncryptedResponse(data, Callback")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
|
|
|
{
|
|
|
|
#if DEBUG
|
|
|
|
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
|
|
#else
|
|
|
|
if(challenge.protectionSpace.host.contains("pba-simulator.patientenbefundapp.labor-limbach-hannover.de"))
|
|
|
|
{
|
|
|
|
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
completionHandler(.performDefaultHandling, nil)
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends Key Exchange Post request to the server in async mode and calls callback(onSucces or onError)
|
|
|
|
*/
|
|
|
|
public class PostKeyExchangeRequestAsync: NSObject, URLSessionDelegate
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Runs 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 _keyPair - Curve25519 KeyPair (Private & Public Keys)
|
|
|
|
* @param onSuccess - onSuccess callback that contains public key of response(Server)
|
|
|
|
* @param onError - onError callback that contains error message
|
|
|
|
*/
|
|
|
|
func RunAsync(host: Servers, controller: String, action: String, _keyPair: Core.Security.Curve25519.KeyPair? = nil, onSuccess: ((Core.Models.Response.PublicKey)->Void)? = nil, onError: ((String)->Void)? = nil)
|
|
|
|
{
|
|
|
|
var keyPair = _keyPair;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if(keyPair == nil)
|
|
|
|
{
|
|
|
|
keyPair = Security.Curve25519.GenerateKeyPair()
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
|
|
request.httpBody = try JSONEncoder().encode(Core.Models.Request.KeyExchange())
|
|
|
|
|
|
|
|
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
|
|
|
|
|
|
|
DispatchQueue.global(qos: .background).async
|
|
|
|
{
|
|
|
|
let task = session.dataTask(with: request)
|
|
|
|
{
|
|
|
|
if($2 != nil)
|
|
|
|
{
|
|
|
|
self.responseError(message: $2!.localizedDescription, callback: onError)
|
|
|
|
Log.Error(err: $2!, namespace: "Core.Https.Request.PostKeyExchangeRequestAsync", method: "RunAsync")
|
|
|
|
}
|
|
|
|
|
|
|
|
if($1 != nil)
|
|
|
|
{
|
|
|
|
let httpUrlResponse = $1 as? HTTPURLResponse
|
|
|
|
|
|
|
|
if(httpUrlResponse != nil)
|
|
|
|
{
|
|
|
|
if(httpUrlResponse!.statusCode == 429)
|
|
|
|
{
|
|
|
|
AppDelegate.Session.Maintenance = false
|
|
|
|
AppDelegate.Session.MaintenanceNotified = false
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "ERROR_TOO_MANY_REQUESTS"), callback: onError)
|
|
|
|
}
|
|
|
|
else if (httpUrlResponse!.statusCode == 503)
|
|
|
|
{
|
|
|
|
AppDelegate.Session.Maintenance = true
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "MSG_MAINTENANCE"), callback: onError)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
AppDelegate.Session.Maintenance = false
|
|
|
|
AppDelegate.Session.MaintenanceNotified = false
|
|
|
|
|
|
|
|
if(!Core.Https.Request.validServerHeaders(server: httpUrlResponse!))
|
|
|
|
{
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"), callback: onError)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if($0 != nil)
|
|
|
|
{
|
|
|
|
self.responsePublicKey(data: $0!, callback: onSuccess)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self.responseError(message: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"), callback: onError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
task.resume()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
Log.Error(err: error, namespace: "Core.Https.Request.PostKeyExchangeRequestAsync", method: "RunAsync")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls OnError Handler with a error message
|
|
|
|
*
|
|
|
|
* @param message - error message
|
|
|
|
* @callback - callback of error response
|
|
|
|
*/
|
|
|
|
private func responseError(message: String, callback: ((String)->Void)? = nil)
|
|
|
|
{
|
|
|
|
if(callback != nil)
|
|
|
|
{
|
|
|
|
callback!(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls OnSuccess Handler with a PublicKey
|
|
|
|
* @param publicKey - Public Key of the server
|
|
|
|
*/
|
|
|
|
private func responsePublicKey(data: Data, callback: ((Core.Models.Response.PublicKey)->Void)? = nil)
|
|
|
|
{
|
|
|
|
if(callback != nil)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
let publicKey = try JSONDecoder().decode(Core.Models.Response.PublicKey.self, from: data)
|
|
|
|
callback!(publicKey)
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
Log.Error(err: error, namespace: "Core.Https.Request.PostKeyExchangeRequestAsync", method: "responsePublicKey(data, Callback")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-03 12:05:19 +02:00
|
|
|
/*
|
2024-01-29 16:20:42 +01:00
|
|
|
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
|
|
|
{
|
|
|
|
#if DEBUG
|
|
|
|
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
|
|
#else
|
|
|
|
if(challenge.protectionSpace.host.contains("pba-simulator.patientenbefundapp.labor-limbach-hannover.de"))
|
|
|
|
{
|
|
|
|
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
completionHandler(.performDefaultHandling, nil)
|
|
|
|
}
|
|
|
|
#endif
|
2024-06-03 12:05:19 +02:00
|
|
|
}*/
|
2024-01-29 16:20:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Forms a controller action for the request
|
|
|
|
* @param host - Host of URL
|
|
|
|
* @param controller - Controller of request
|
|
|
|
* @param action - Action of Controller, that should be called
|
|
|
|
* @return returns well formed url for the request
|
|
|
|
*/
|
|
|
|
public static func wellFormedControllerAction(host: Servers, controller: String?, action: String?) -> String
|
|
|
|
{
|
|
|
|
var result: String = "https://" + host.rawValue;
|
|
|
|
|
|
|
|
if(controller != nil && !controller!.isEmpty)
|
|
|
|
{
|
|
|
|
result = result + "/" + controller!
|
|
|
|
}
|
|
|
|
|
|
|
|
if(action != nil && !action!.isEmpty)
|
|
|
|
{
|
|
|
|
result = result + "/" + action!
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Verifies server header
|
|
|
|
* @param server - server that sends response to the client
|
|
|
|
* @return returns true if server header and signature are valid
|
|
|
|
*/
|
|
|
|
public static func validServerHeaders(server: HTTPURLResponse) -> Bool
|
|
|
|
{
|
|
|
|
var result: Bool = false
|
|
|
|
|
|
|
|
let userAgent = server.value(forHTTPHeaderField: "User-Agent")
|
|
|
|
let contentType = server.value(forHTTPHeaderField: "Content-Type")
|
|
|
|
let serverHash = server.value(forHTTPHeaderField: "Server-Hash")
|
|
|
|
let serverKey = server.value(forHTTPHeaderField: "Server-Key")
|
|
|
|
|
|
|
|
if(userAgent != nil &&
|
|
|
|
userAgent == "BefundAppServer" &&
|
|
|
|
contentType != nil &&
|
|
|
|
contentType == "application/json" &&
|
|
|
|
serverHash != nil &&
|
|
|
|
HeaderValueSecureHash == Security.AES.Decrypt(value: serverHash!, deriveKey: SymmetricKey(data: Security.AES.GetKey(password: HeaderSecureHash)))
|
|
|
|
&& serverKey != nil)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validates the server response by signature and HMAC
|
|
|
|
*
|
|
|
|
* @param server - server that sends response to the client
|
|
|
|
* @param encryptedResponse - encrypted response with hmac
|
|
|
|
* @param deriveKey - derive key with server
|
|
|
|
* @return returns true if valid
|
|
|
|
*/
|
|
|
|
public static func validServerSignature(server: HTTPURLResponse, encryptedResponse: Core.Models.Response.EncryptedResponse, deriveKey: SymmetricKey) -> Bool
|
|
|
|
{
|
|
|
|
var result: Bool = false
|
|
|
|
|
|
|
|
let signature = server.value(forHTTPHeaderField: "Server-Signature")
|
|
|
|
let signatureKey = server.value(forHTTPHeaderField: "Server-Signature-Key")
|
|
|
|
|
|
|
|
if(signature != nil &&
|
|
|
|
!signature!.isEmpty &&
|
|
|
|
signatureKey != nil &&
|
|
|
|
!signatureKey!.isEmpty &&
|
|
|
|
encryptedResponse.ValidSignature(deriveKey: deriveKey, clientSignature: signature!, clientSignatureKey: signatureKey!))
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|