diff --git a/.DS_Store b/.DS_Store index 50d839a..c762b0b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Befund.xcodeproj/project.pbxproj b/Befund.xcodeproj/project.pbxproj index fc8e4ee..c8a7913 100644 --- a/Befund.xcodeproj/project.pbxproj +++ b/Befund.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ 6E2A3E2B28A706FB002EB204 /* downArrow@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6E2A3E2228A706FB002EB204 /* downArrow@3x.png */; }; 6E2A3E2D28A706FB002EB204 /* downArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 6E2A3E2428A706FB002EB204 /* downArrow.png */; }; 6E31A84C28CF1D0F0046CB23 /* UIImageWithAlpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E31A84B28CF1D0F0046CB23 /* UIImageWithAlpha.swift */; }; + 6E37C7182BFDFA9600433471 /* RequestAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37C7172BFDFA9600433471 /* RequestAsync.swift */; }; 6E3BBFB128A2657B0010B7F2 /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3BBFB028A2657B0010B7F2 /* UISlider.swift */; }; 6E3BBFB328A265BE0010B7F2 /* UISlider.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6E3BBFB228A265BE0010B7F2 /* UISlider.xib */; }; 6E3CAB64287C7E3300E3064D /* Labor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3CAB63287C7E3300E3064D /* Labor.swift */; }; @@ -184,6 +185,7 @@ 6E2A3E2428A706FB002EB204 /* downArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = downArrow.png; sourceTree = ""; }; 6E2A3E2F28A70C22002EB204 /* Befund-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Befund-Bridging-Header.h"; sourceTree = ""; }; 6E31A84B28CF1D0F0046CB23 /* UIImageWithAlpha.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageWithAlpha.swift; sourceTree = ""; }; + 6E37C7172BFDFA9600433471 /* RequestAsync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAsync.swift; sourceTree = ""; }; 6E3BBFB028A2657B0010B7F2 /* UISlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISlider.swift; sourceTree = ""; }; 6E3BBFB228A265BE0010B7F2 /* UISlider.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UISlider.xib; sourceTree = ""; }; 6E3CAB63287C7E3300E3064D /* Labor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Labor.swift; sourceTree = ""; }; @@ -259,6 +261,7 @@ children = ( 694FF35F282F86D40070C3F2 /* Request.swift */, 6949A0F6286A079D00987D04 /* Servers.swift */, + 6E37C7172BFDFA9600433471 /* RequestAsync.swift */, ); path = Https; sourceTree = ""; @@ -718,6 +721,7 @@ 698D54F428301C6800766CE3 /* Subscribe.swift in Sources */, 69AB1FA028422AAC008045E1 /* Download.swift in Sources */, 698D54F2283015E000766CE3 /* EncryptedRequest.swift in Sources */, + 6E37C7182BFDFA9600433471 /* RequestAsync.swift in Sources */, 699DCADE283F7DFF0072D121 /* Results.swift in Sources */, 6EA6D4EF289BC8C80009F0E7 /* Support.swift in Sources */, 6E3CAB662889AD6A00E3064D /* SettingsController.swift in Sources */, @@ -929,7 +933,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W86CN3N7WD; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Befund/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Patienten Befundapp"; + INFOPLIST_KEY_CFBundleDisplayName = "Limbach Befund2Go"; INFOPLIST_KEY_NSCameraUsageDescription = "Die Kamera wird verwendet, um den QR-Code des Labors zu scannen"; INFOPLIST_KEY_NSFaceIDUsageDescription = "Biometrische Daten sind notwendig um die PIN anzufordern"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -970,7 +974,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W86CN3N7WD; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Befund/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Patienten Befundapp"; + INFOPLIST_KEY_CFBundleDisplayName = "Limbach Befund2Go"; INFOPLIST_KEY_NSCameraUsageDescription = "Die Kamera wird verwendet, um den QR-Code des Labors zu scannen"; INFOPLIST_KEY_NSFaceIDUsageDescription = "Biometrische Daten sind notwendig um die PIN anzufordern"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/Befund/Assets.xcassets/limbach-gruppe-logo.imageset/.DS_Store b/Befund/Assets.xcassets/limbach-gruppe-logo.imageset/.DS_Store index 28f6505..143aa19 100644 Binary files a/Befund/Assets.xcassets/limbach-gruppe-logo.imageset/.DS_Store and b/Befund/Assets.xcassets/limbach-gruppe-logo.imageset/.DS_Store differ diff --git a/Befund/Controllers/ScannerController.swift b/Befund/Controllers/ScannerController.swift index f8701c9..af18ab8 100644 --- a/Befund/Controllers/ScannerController.swift +++ b/Befund/Controllers/ScannerController.swift @@ -183,7 +183,7 @@ class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDel } else { - self.found(code: "befund://labor?id=LIS_SIMULATOR") + self.found(code: "befund://labor?id=DEVELOPMENT") } //self.found(code: "befund://labor?id=LABOR_MOENCHENGLADBACH") #endif diff --git a/Befund/Core/.DS_Store b/Befund/Core/.DS_Store index eaf6791..8dff5ec 100644 Binary files a/Befund/Core/.DS_Store and b/Befund/Core/.DS_Store differ diff --git a/Befund/Core/Https/Request.swift b/Befund/Core/Https/Request.swift index 20630f5..3bb84ae 100644 --- a/Befund/Core/Https/Request.swift +++ b/Befund/Core/Https/Request.swift @@ -350,7 +350,7 @@ extension Core } } } - +/* public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { #if DEBUG @@ -365,7 +365,7 @@ extension Core completionHandler(.performDefaultHandling, nil) } #endif - } + }*/ } /** diff --git a/Befund/Core/Https/RequestAsync.swift b/Befund/Core/Https/RequestAsync.swift index 99fa63c..d1326fc 100644 --- a/Befund/Core/Https/RequestAsync.swift +++ b/Befund/Core/Https/RequestAsync.swift @@ -1,9 +1,287 @@ // -// File.swift +// Request.swift // Befund // -// Created by Artur Savitskiy on 22.05.24. -// Copyright © 2024 MVZ Dr. Stein und Kollegen. All rights reserved. -// +// 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 + } + + } + } + } +} diff --git a/Befund/Core/Https/Servers.swift b/Befund/Core/Https/Servers.swift index 1bdcc37..ec0f8d7 100644 --- a/Befund/Core/Https/Servers.swift +++ b/Befund/Core/Https/Servers.swift @@ -13,7 +13,7 @@ extension Core.Https public enum Servers : String { // Development server - case DEVELOPMENT = "192.168.178.86" + case DEVELOPMENT = "192.168.0.169" /** * HOST´s of productive outside services diff --git a/Befund/Core/Models/Labor.swift b/Befund/Core/Models/Labor.swift index fa4fc6f..2d84db6 100644 --- a/Befund/Core/Models/Labor.swift +++ b/Befund/Core/Models/Labor.swift @@ -13,7 +13,7 @@ extension Core.Models { public class Labor { - public var id: String = "" + public var id: String = "DEVELOPMENT" public var name: String = "" public var street: String = "" public var city: String = "" diff --git a/Befund/Screens/Base.lproj/LaunchScreen.storyboard b/Befund/Screens/Base.lproj/LaunchScreen.storyboard index a4a3671..3c201f5 100644 --- a/Befund/Screens/Base.lproj/LaunchScreen.storyboard +++ b/Befund/Screens/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -26,8 +26,8 @@ - diff --git a/Befund/Screens/Base.lproj/Main.storyboard b/Befund/Screens/Base.lproj/Main.storyboard index 7cf0d1f..74d5fcf 100644 --- a/Befund/Screens/Base.lproj/Main.storyboard +++ b/Befund/Screens/Base.lproj/Main.storyboard @@ -201,7 +201,7 @@ - @@ -2963,7 +2963,7 @@ - + @@ -3162,7 +3162,7 @@ - +