From c4381f6abb1f9ba2548fba2a11d2c04f6dd951b0 Mon Sep 17 00:00:00 2001 From: NMerz Date: Fri, 10 Apr 2020 13:30:52 -0400 Subject: [PATCH 01/24] Initial demo of secure key generation from password Will be used with symetric key encryption to encrypt primary key for storage --- DoctorsNote/DoctorsNote/AppDelegate.swift | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DoctorsNote/DoctorsNote/AppDelegate.swift b/DoctorsNote/DoctorsNote/AppDelegate.swift index ad94777f..3c3296a7 100644 --- a/DoctorsNote/DoctorsNote/AppDelegate.swift +++ b/DoctorsNote/DoctorsNote/AppDelegate.swift @@ -16,6 +16,11 @@ import AWSCognito import AWSMobileClient import UserNotifications +import CryptoKit +import CommonCrypto + + + extension AWSMobileClientError { var message: String { switch self { @@ -86,6 +91,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let saltString = "username" + let saltData = saltString.data(using: .utf8)! + let passwordString = "abcd" + let password = passwordString.data(using: .utf8)! + print (SHA512.hash(data: password)) + print (SHA512.hash(data: password)) + print ((SHA512.hash(data: password).underestimatedCount)) + var newKey = UnsafeMutablePointer.allocate(capacity: 256) + var saltValue = [UInt8]() + for char in saltString.cString(using: .utf8)! { + saltValue.append(UInt8(char)) + } + print(saltValue) + print(CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordString, passwordString.count, saltValue, saltString.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512), 200000, newKey, 256)) + print(newKey.pointee) + print(newKey.advanced(by: 255).pointee) + print(String(data: Data(bytes: newKey, count: 256), encoding: .utf8)) + + + + + requestNotificationAuthorization(application: application) // Override point for customization after application launch. From e3336a37eb89519823a7acfe5185e851c5b69b53 Mon Sep 17 00:00:00 2001 From: NMerz Date: Fri, 10 Apr 2020 19:53:19 -0400 Subject: [PATCH 02/24] Add public key gen poc May need to do once per convo --- DoctorsNote/DoctorsNote/AppDelegate.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/DoctorsNote/DoctorsNote/AppDelegate.swift b/DoctorsNote/DoctorsNote/AppDelegate.swift index 3c3296a7..798ed407 100644 --- a/DoctorsNote/DoctorsNote/AppDelegate.swift +++ b/DoctorsNote/DoctorsNote/AppDelegate.swift @@ -104,10 +104,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate { saltValue.append(UInt8(char)) } print(saltValue) + print(CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordString, passwordString.count, saltValue, saltString.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512), 200000, newKey, 256)) print(newKey.pointee) print(newKey.advanced(by: 255).pointee) print(String(data: Data(bytes: newKey, count: 256), encoding: .utf8)) + let attributes = + [ kSecAttrKeyType: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits: 2048 + ] as [CFString : Any]; + let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, UnsafeMutablePointer?>.allocate(capacity: 100)) + let publicKey = SecKeyCopyPublicKey(privateKey!) + print((SecKeyCopyExternalRepresentation(privateKey!, UnsafeMutablePointer?>.allocate(capacity: 100))! as! Data).base64EncodedString()) + print((SecKeyCopyExternalRepresentation(publicKey!, UnsafeMutablePointer?>.allocate(capacity: 100))! as! Data).base64EncodedString()) + let localAESKey = Data(bytes: newKey, count: 256) + print(localAESKey.base64EncodedString()) From 7fe4764673ef20622b8ab236b0138e584eab957e Mon Sep 17 00:00:00 2001 From: NMerz Date: Sun, 12 Apr 2020 16:50:39 -0400 Subject: [PATCH 03/24] POC on private key encryption Server connections and proper cipher class coming soon --- DoctorsNote/DoctorsNote/AppDelegate.swift | 55 ++++++++++++++++++----- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/DoctorsNote/DoctorsNote/AppDelegate.swift b/DoctorsNote/DoctorsNote/AppDelegate.swift index 798ed407..86998aaf 100644 --- a/DoctorsNote/DoctorsNote/AppDelegate.swift +++ b/DoctorsNote/DoctorsNote/AppDelegate.swift @@ -109,21 +109,54 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print(newKey.pointee) print(newKey.advanced(by: 255).pointee) print(String(data: Data(bytes: newKey, count: 256), encoding: .utf8)) - let attributes = - [ kSecAttrKeyType: kSecAttrKeyTypeRSA, - kSecAttrKeySizeInBits: 2048 + let attributes = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits: 2048, + kSecAttrKeyClass: kSecAttrKeyClassPrivate ] as [CFString : Any]; - let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, UnsafeMutablePointer?>.allocate(capacity: 100)) - let publicKey = SecKeyCopyPublicKey(privateKey!) - print((SecKeyCopyExternalRepresentation(privateKey!, UnsafeMutablePointer?>.allocate(capacity: 100))! as! Data).base64EncodedString()) - print((SecKeyCopyExternalRepresentation(publicKey!, UnsafeMutablePointer?>.allocate(capacity: 100))! as! Data).base64EncodedString()) + let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, UnsafeMutablePointer?>.allocate(capacity: 100))! + let publicKey = SecKeyCopyPublicKey(privateKey) + var error: Unmanaged? + let cfExport = SecKeyCopyExternalRepresentation(privateKey, &error)! + let privateKeyExport = (cfExport as Data) + print(error) + print(privateKeyExport.base64EncodedString()) + print((SecKeyCopyExternalRepresentation(publicKey!, UnsafeMutablePointer?>.allocate(capacity: 100))! as Data).base64EncodedString()) let localAESKey = Data(bytes: newKey, count: 256) print(localAESKey.base64EncodedString()) - - - - + var toEncrypt = privateKeyExport.base64EncodedString() + print("key size") + print(toEncrypt.count) + toEncrypt.reserveCapacity(toEncrypt.count / 128 * 128 + 128) + let encrypted = UnsafeMutablePointer.allocate(capacity: (toEncrypt.count / 128 * 128 + 128)) + var bytesEncrypted = 0 + let encryptReturn = CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES128), CCOptions(), newKey, kCCKeySizeAES256, saltValue, toEncrypt, toEncrypt.count / 128 * 128 + 128, encrypted, (toEncrypt.count / 128 * 128 + 128), &bytesEncrypted) + print (Data(base64Encoded: toEncrypt)!.base64EncodedString()) + print("Encypted Return:") + print(encryptReturn) + print(bytesEncrypted) + let encryptedText = Data(bytes: encrypted, count: (toEncrypt.count / 128 * 128 + 128)) + print(encryptedText.base64EncodedString()) + let decrypted = UnsafeMutablePointer.allocate(capacity: (toEncrypt.count / 128 * 128 + 128)) + let decryptReturn = CCCrypt(CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmAES128), CCOptions(), newKey, kCCKeySizeAES256, saltValue, encrypted, (toEncrypt.count / 128 * 128 + 128), decrypted, (toEncrypt.count / 128 * 128 + 128), &bytesEncrypted) //Note: this currently takes a nonbase64 data object, but the return from the API will be in base64. I just have to remember to use the base64 interpretation on it + //To expand on the above: Export -> Base64->encode->base64->store->retrieve->non-base64->decode->nonbase64->import + print("Decrypt return:") + print(decryptReturn) + let baseDecrypt = Data(bytes: decrypted, count: toEncrypt.count) + print(baseDecrypt.base64EncodedString()) + let decryptedText = Data(base64Encoded: baseDecrypt)! //Data(base64Encoded: String(data: Data(bytes:decrypted, count: toEncrypt.count / 128 * 128 + 128), encoding: .utf8)!)! + print(decryptedText.base64EncodedString()) + var unmanagedError: Unmanaged? = nil + let newPrivateKey = SecKeyCreateWithData(decryptedText as CFData, attributes as CFDictionary, UnsafeMutablePointer?>(&unmanagedError)) + print(unmanagedError) + if decryptedText == privateKeyExport { + print("Matching data") + } + if newPrivateKey! == privateKey { + print("success") + } else { + print((SecKeyCopyExternalRepresentation(newPrivateKey!, UnsafeMutablePointer?>.allocate(capacity: 100))! as Data).base64EncodedString()) + } requestNotificationAuthorization(application: application) // Override point for customization after application launch. From c574d815eb5f8171b939524db6df9a8a68b0228d Mon Sep 17 00:00:00 2001 From: NMerz Date: Sun, 12 Apr 2020 18:24:33 -0400 Subject: [PATCH 04/24] Initial message cipher work Message cipher set up. Connection Proccessor updates started (Unit tests not up to date and calls for message methods not updated). Will need a seperate cipher and connection methods to handle the AES key creation and RSA key updating. Will also need to provide knowledge of dependencies to areas where called --- .../DoctorsNote.xcodeproj/project.pbxproj | 6 ++ DoctorsNote/DoctorsNote/AppDelegate.swift | 6 +- .../DoctorsNote/ChatLogController.swift | 10 +-- .../DoctorsNote/ConnectionProcessor.swift | 54 ++++++++++- DoctorsNote/DoctorsNote/MessageCipher.swift | 89 +++++++++++++++++++ 5 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 DoctorsNote/DoctorsNote/MessageCipher.swift diff --git a/DoctorsNote/DoctorsNote.xcodeproj/project.pbxproj b/DoctorsNote/DoctorsNote.xcodeproj/project.pbxproj index 97806ad6..64a35513 100644 --- a/DoctorsNote/DoctorsNote.xcodeproj/project.pbxproj +++ b/DoctorsNote/DoctorsNote.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 9D23056E24105ED00064E6E2 /* Reminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D23056D24105ED00064E6E2 /* Reminder.swift */; }; 9D267F0F24105CD000E787FB /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D267F0E24105CD000E787FB /* Message.swift */; }; 9D325395242A765F0057B354 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D267F0124105BDA00E787FB /* User.swift */; }; + 9D41A4A62443BA880069A1B8 /* MessageCipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D41A4A52443BA880069A1B8 /* MessageCipher.swift */; }; + 9D41A4A72443BA8B0069A1B8 /* MessageCipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D41A4A52443BA880069A1B8 /* MessageCipher.swift */; }; 9D90C39B2400666B006FD4C6 /* ConnectionProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90C38B23F9CF8B006FD4C6 /* ConnectionProcessorTests.swift */; }; 9DD1D469242E8B3200925298 /* Appointment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8E3BA28242C258900820F05 /* Appointment.swift */; }; AD4567D3634346E5B3E37C80 /* Pods_DoctorsNoteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A21B97793A0B6E9F53489841 /* Pods_DoctorsNoteTests.framework */; }; @@ -112,6 +114,7 @@ 9D23056D24105ED00064E6E2 /* Reminder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reminder.swift; sourceTree = ""; }; 9D267F0124105BDA00E787FB /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 9D267F0E24105CD000E787FB /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + 9D41A4A52443BA880069A1B8 /* MessageCipher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCipher.swift; sourceTree = ""; }; 9D90C38B23F9CF8B006FD4C6 /* ConnectionProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionProcessorTests.swift; sourceTree = ""; }; A21B97793A0B6E9F53489841 /* Pods_DoctorsNoteTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DoctorsNoteTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B50173F82408323F003D2224 /* SearchGroupsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchGroupsTableViewController.swift; sourceTree = ""; }; @@ -260,6 +263,7 @@ 442F306523F883A8001EBC3C /* ConversationViewController.swift */, B82083CA24033A1E00CF41D0 /* SupportGroupMessageVC.swift */, 447323FF243184900026DA34 /* SupportGroupConvo.swift */, + 9D41A4A52443BA880069A1B8 /* MessageCipher.swift */, ); path = DoctorsNote; sourceTree = ""; @@ -632,6 +636,7 @@ B85C61B5242DDB160002D014 /* AppointmentListCell.swift in Sources */, B54E4F262408ED8300349316 /* DefinedValues.swift in Sources */, 9D137F45242BAC2000A0B0AB /* HealthSystem.swift in Sources */, + 9D41A4A62443BA880069A1B8 /* MessageCipher.swift in Sources */, B587B55A2429710C00B6BF0D /* EditReminderVC.swift in Sources */, B54E4F3A2408EEA900349316 /* SearchGroupsTableViewController.swift in Sources */, B85C61B7242DE6950002D014 /* AppointmentListVC.swift in Sources */, @@ -674,6 +679,7 @@ B51C6A22242C574B00048BCF /* (null) in Sources */, B51C6A23242C574B00048BCF /* (null) in Sources */, B5AE734E24156C5B00A9CDAE /* (null) in Sources */, + 9D41A4A72443BA8B0069A1B8 /* MessageCipher.swift in Sources */, 9D137F40242BAB6E00A0B0AB /* ConnectionProcessor.swift in Sources */, 9D137F43242BAB7700A0B0AB /* Conversation.swift in Sources */, 9D137F89242C020A00A0B0AB /* Reminder.swift in Sources */, diff --git a/DoctorsNote/DoctorsNote/AppDelegate.swift b/DoctorsNote/DoctorsNote/AppDelegate.swift index 86998aaf..1c4d8d2e 100644 --- a/DoctorsNote/DoctorsNote/AppDelegate.swift +++ b/DoctorsNote/DoctorsNote/AppDelegate.swift @@ -112,7 +112,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let attributes = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeySizeInBits: 2048, kSecAttrKeyClass: kSecAttrKeyClassPrivate - ] as [CFString : Any]; + ] as [CFString : Any] let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, UnsafeMutablePointer?>.allocate(capacity: 100))! let publicKey = SecKeyCopyPublicKey(privateKey) var error: Unmanaged? @@ -129,7 +129,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { toEncrypt.reserveCapacity(toEncrypt.count / 128 * 128 + 128) let encrypted = UnsafeMutablePointer.allocate(capacity: (toEncrypt.count / 128 * 128 + 128)) var bytesEncrypted = 0 - let encryptReturn = CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES128), CCOptions(), newKey, kCCKeySizeAES256, saltValue, toEncrypt, toEncrypt.count / 128 * 128 + 128, encrypted, (toEncrypt.count / 128 * 128 + 128), &bytesEncrypted) + var AESKey = UnsafeMutableBufferPointer.allocate(capacity: 256) + localAESKey.copyBytes(to: AESKey) + let encryptReturn = CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES128), CCOptions(), AESKey.baseAddress, kCCKeySizeAES256, saltValue, toEncrypt, toEncrypt.count / 128 * 128 + 128, encrypted, (toEncrypt.count / 128 * 128 + 128), &bytesEncrypted) //String(data: localAESKey, encoding: .utf8) is untested. Used to be just newKey print (Data(base64Encoded: toEncrypt)!.base64EncodedString()) print("Encypted Return:") print(encryptReturn) diff --git a/DoctorsNote/DoctorsNote/ChatLogController.swift b/DoctorsNote/DoctorsNote/ChatLogController.swift index 2291a467..68b2fba8 100644 --- a/DoctorsNote/DoctorsNote/ChatLogController.swift +++ b/DoctorsNote/DoctorsNote/ChatLogController.swift @@ -53,7 +53,7 @@ class ChatLogController: UIViewController, UICollectionViewDelegate, UICollectio // Do any additional setup after loading the view. messagesShown = 20; do { - messages = try (connectionProcessor.processMessages(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messagelist/", conversationID: conversation!.getConversationID(), numberToRetrieve: messagesShown) ?? messages) + messages = try (connectionProcessor.processMessages(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messagelist/", conversationID: conversation!.getConversationID(), numberToRetrieve: messagesShown, cipher: MessageCipher(uniqueID: "replaceMe", localAESKey: Data())) ?? messages) //TODO: Fill in cipher parameters print(messages) } catch let error { print ((error as! ConnectionError).getMessage()) @@ -91,7 +91,7 @@ class ChatLogController: UIViewController, UICollectionViewDelegate, UICollectio } let newMessage = Message(messageID: -1, conversationID: conversation!.getConversationID(), content: (messageText!.text?.data(using: .utf8))!, contentType: 0)//TODO: Needs conversationID to be passed in dynamically based on the current conversation print(newMessage.getBase64Content()) - let err = connectionProcessor.processNewMessage(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messageadd", message: newMessage) + let err = connectionProcessor.processNewMessage(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messageadd", message: newMessage, cipher: MessageCipher(uniqueID: "replaceMe", localAESKey: Data())) if (err != nil) { let alertController = UIAlertController(title: "Error Sending Message", message: "The message failed to send.", preferredStyle: .alert) let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil) @@ -140,7 +140,7 @@ class ChatLogController: UIViewController, UICollectionViewDelegate, UICollectio let newMessage = Message(messageID: -1, conversationID: conversation!.getConversationID(), content: content, contentType: 1) //TODO: Needs conversationID to be passed in dynamically based on the current conversation //print(newMessage.getContent()) - let potentialError = connectionProcessor.processNewMessage(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messageadd", message: newMessage) + let potentialError = connectionProcessor.processNewMessage(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messageadd", message: newMessage, cipher: MessageCipher(uniqueID: "replaceMe", localAESKey: Data())) if (potentialError != nil) { print(potentialError?.getMessage()) } @@ -151,8 +151,8 @@ class ChatLogController: UIViewController, UICollectionViewDelegate, UICollectio func reloadMessages() { messagesShown += 1 do { - messages = try connectionProcessor.processMessages(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messagelist/", conversationID: conversation!.getConversationID(), numberToRetrieve: messagesShown) - print(try connectionProcessor.processMessages(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messagelist/", conversationID: conversation!.getConversationID(), numberToRetrieve: messagesShown)) + messages = try connectionProcessor.processMessages(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/messagelist/", conversationID: conversation!.getConversationID(), numberToRetrieve: messagesShown, cipher: MessageCipher(uniqueID: "replaceMe", localAESKey: Data())) + print(messages) } catch let error { print ((error as! ConnectionError).getMessage()) print("ERROR!!!!!!!!!!!!") diff --git a/DoctorsNote/DoctorsNote/ConnectionProcessor.swift b/DoctorsNote/DoctorsNote/ConnectionProcessor.swift index 536b5c04..60ec958e 100644 --- a/DoctorsNote/DoctorsNote/ConnectionProcessor.swift +++ b/DoctorsNote/DoctorsNote/ConnectionProcessor.swift @@ -162,10 +162,11 @@ class ConnectionProcessor { // let conversation = conversationList[conversationKey] as! [String : Any?] print(conversation["conversationID"] as? Int) print(conversation["converserID"] as? String) + print(conversation["publicKey"] as? String) print(conversation["conversationName"] as? String) print(conversation["lastMessageTime"] as? TimeInterval) print(conversation["status"] as? Int) - if ((conversation["conversationID"] as? Int) != nil) && ((conversation["converserID"] as? String) != nil) && ((conversation["conversationName"] as? String) != nil) && ((conversation["lastMessageTime"] as? TimeInterval) != nil) && ((conversation["status"] as? Int) != nil) { + if ((conversation["conversationID"] as? Int) != nil) && ((conversation["converserID"] as? String) != nil) && ((conversation["publicKey"] as? String) != nil) && ((conversation["conversationName"] as? String) != nil) && ((conversation["lastMessageTime"] as? TimeInterval) != nil) && ((conversation["status"] as? Int) != nil) { let newConversation = Conversation(conversationID: conversation["conversationID"] as! Int, converserID: conversation["converserID"] as! String, conversationName: conversation["conversationName"] as! String, lastMessageTime: Date(timeIntervalSince1970: (conversation["lastMessageTime"] as! TimeInterval) / 1000.0), status: conversation["status"] as! Int) conversations.append(newConversation) } else { @@ -185,7 +186,7 @@ class ConnectionProcessor { return (Conversation(conversationID: -1, converserID: "-1", conversationName: "placeholder retrieval", lastMessageTime: Date(), status: -999), nil) } - func processMessages(url: String, conversationID: Int, numberToRetrieve: Int, startIndex: Int = 0, sinceWhen: Date = Date(timeIntervalSinceNow: TimeInterval(0))) throws -> [Message] { + func processMessages(url: String, conversationID: Int, numberToRetrieve: Int, cipher: MessageCipher, startIndex: Int = 0, sinceWhen: Date = Date(timeIntervalSinceNow: TimeInterval(0))) throws -> [Message] { var messageJSON = [String : Any]() messageJSON["conversationID"] = conversationID messageJSON["numberToRetrieve"] = numberToRetrieve @@ -212,6 +213,17 @@ class ConnectionProcessor { print((message["contentType"] as? Int) != nil) print((message["sender"] as? String) != nil) if ((message["messageId"] as? Int) != nil) && ((message["content"] as? String) != nil) && Data(base64Encoded: (message["content"] as! String)) != nil && ((message["contentType"] as? Int) != nil) && ((message["sender"] as? String) != nil) { + let encryptedMessage = String(data: Data(base64Encoded: (message["content"] as! String))!, encoding: .utf8)! + let messageBase64: String + do { + messageBase64 = try cipher.decrypt(toDecrypt: encryptedMessage) + } catch let error as CipherError { + throw ConnectionError(message: error.getMessage()) + } + print(Data(base64Encoded: messageBase64) != nil) + if Data(base64Encoded: messageBase64) != nil { + throw ConnectionError(message: "Message JSON field did not meet encryption, encoding, and/or formatting requirements") + } let newMessage = Message(messageID: message["messageId"] as! Int, conversationID: conversationID, content: Data(base64Encoded: (message["content"] as! String))!, contentType: message["contentType"] as! Int, sender: User(uid: message["sender"] as! String)) messages.append(newMessage) } else { @@ -223,10 +235,16 @@ class ConnectionProcessor { //TODO: Finer processing/passing of any errors returned by server to UI // - Need to discuss this with team - func processNewMessage(url: String, message: Message) -> ConnectionError? { + func processNewMessage(url: String, message: Message, cipher: MessageCipher) -> ConnectionError? { var messageJSON = [String: Any]() messageJSON["conversationID"] = message.getConversationID() - messageJSON["content"] = message.getBase64Content() + let encryptedContent: Data + do { + encryptedContent = try cipher.encrypt(toEncrypt: message.getBase64Content()) + } catch let error { + return ConnectionError(message: (error as! CipherError).getMessage()) + } + messageJSON["content"] = encryptedContent.base64EncodedString() messageJSON["contentType"] = message.getContentType() do { let data = try postData(urlString: url, dataJSON: messageJSON) @@ -369,6 +387,34 @@ class ConnectionProcessor { } return appointments } + + //Returns the two encryptions of the private key (base64) data (in base64 format) + //Note: exact formatting may change and this method should make no assumptions nor do validation + func retrieveEncryptedPrivateKeys(url: String) throws -> (String, String) { + let emptyJSON = [String: Any]() + let data = try postData(urlString: url, dataJSON: emptyJSON) + if data.first?.value as? [String : Any?] == nil { + throw ConnectionError(message: "Incorrect JSON format -- expected single level dictionary object") + } + let keyJSON = data.first?.value as! [String : Any?] + if keyJSON["privateKeyP"] as? String == nil || keyJSON["privateKeyS"] as? String == nil { + throw ConnectionError(message: "At least one JSON field was missing or in an incorrect format") + } + return (keyJSON["privateKeyP"] as! String, keyJSON["privateKeyS"] as! String) + } + + func postKeys(url: String, privateKeyP: String , privateKeyS: String, publicKey: String) throws { + var keyJSON = [String: Any]() + keyJSON["privateKeyP"] = privateKeyP + keyJSON["privateKeyS"] = privateKeyS + keyJSON["publicKey"] = publicKey + + let data = try postData(urlString: url, dataJSON: keyJSON) + if data.count != 0 { + throw ConnectionError(message: "Non-blank return") + } + //Should have returned a blank 200 if successful, if so, no need to do anything + } } class Connector { diff --git a/DoctorsNote/DoctorsNote/MessageCipher.swift b/DoctorsNote/DoctorsNote/MessageCipher.swift new file mode 100644 index 00000000..296bb50c --- /dev/null +++ b/DoctorsNote/DoctorsNote/MessageCipher.swift @@ -0,0 +1,89 @@ +// +// MessageCipher.swift +// DoctorsNote +// +// Created by Merz on 4/12/20. +// Copyright © 2020 Team7. All rights reserved. +// + +import Foundation +import CryptoKit +import CommonCrypto + +class MessageCipher { + private let localAESKey: Data + + private var saltValue: [UInt8] + + private var privateKey: SecKey? = nil + private var publicKey: SecKey? = nil + + init(uniqueID: String, localAESKey: Data) { + self.localAESKey = localAESKey + saltValue = [UInt8]() + for char in uniqueID.cString(using: .utf8)! { + saltValue.append(UInt8(char)) + } + } + + //Input: Private key data in base64 format -> encrypted -> base64 String + public func setPrivateKey(encryptedPrivateKey: String) throws { + if Data(base64Encoded: encryptedPrivateKey) == nil { + throw CipherError(message: "Input key is note base64 encoded") + } + let toDecryptData = Data(base64Encoded: encryptedPrivateKey)! + let toDecrypt = String(data: toDecryptData, encoding: .utf8)! + + let baseDecrypt = try decodePrivateKey(toDecrypt: toDecrypt) + let decryptedText = Data(base64Encoded: baseDecrypt)! + print(decryptedText.base64EncodedString()) + var unmanagedError: Unmanaged? = nil + let attributes = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits: 2048, + kSecAttrKeyClass: kSecAttrKeyClassPrivate + ] as [CFString : Any] + let newPrivateKey = SecKeyCreateWithData(decryptedText as CFData, attributes as CFDictionary, UnsafeMutablePointer?>(&unmanagedError)) + privateKey = newPrivateKey + } + + public func decodePrivateKey(toDecrypt: String) throws -> String { + let decrypted = UnsafeMutablePointer.allocate(capacity: (toDecrypt.count / 128 * 128 + 128)) + var bytesEncrypted = 0 + var AESKey = UnsafeMutableBufferPointer.allocate(capacity: 256) + localAESKey.copyBytes(to: AESKey) + let decryptReturn = CCCrypt(CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmAES128), CCOptions(), AESKey.baseAddress, kCCKeySizeAES256, saltValue, toDecrypt, (toDecrypt.count / 128 * 128 + 128), decrypted, (toDecrypt.count / 128 * 128 + 128), &bytesEncrypted) //Note: this currently takes a nonbase64 data object, but the return from the API will be in base64. I just have to remember to use the base64 interpretation on it + //To expand on the above: Export -> Base64->encode->base64->store->retrieve->non-base64->decode->nonbase64->import + + print("Decrypt return:") + print(decryptReturn) + let decryptedText = Data(bytes: decrypted, count: toDecrypt.count) + + print(decryptedText.base64EncodedString()) + return String(data:decryptedText, encoding: .utf8)! + } + + public func decrypt(toDecrypt: String) throws -> String { + if privateKey == nil { + throw CipherError(message: "Private key not set.") + } + return String(data: SecKeyCreateDecryptedData(privateKey!, .rsaEncryptionOAEPSHA512, toDecrypt as! CFData, nil)! as Data, encoding: .utf8)! + } + + public func encrypt(toEncrypt: String) throws -> Data { + if publicKey == nil { + throw CipherError(message: "Public key not set.") + } + return SecKeyCreateEncryptedData(privateKey!, .rsaEncryptionOAEPSHA512, toEncrypt as! CFData, nil)! as Data + } + +} + +class CipherError : Error { + private let message: String + init(message: String) { + self.message = message + } + func getMessage() -> String { + return message + } +} From 81756f6e57c6e24419618c71253d087281c7280a Mon Sep 17 00:00:00 2001 From: Ariana Zhu Date: Sun, 12 Apr 2020 15:25:15 -0700 Subject: [PATCH 05/24] ignore xcuserdata --- .../xcschemes/xcschememanagement.plist | 221 ------------------ 1 file changed, 221 deletions(-) delete mode 100644 DoctorsNote/Pods/Pods.xcodeproj/xcuserdata/arianazhu.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/DoctorsNote/Pods/Pods.xcodeproj/xcuserdata/arianazhu.xcuserdatad/xcschemes/xcschememanagement.plist b/DoctorsNote/Pods/Pods.xcodeproj/xcuserdata/arianazhu.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 11b11d67..00000000 --- a/DoctorsNote/Pods/Pods.xcodeproj/xcuserdata/arianazhu.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,221 +0,0 @@ - - - - - SchemeUserState - - AWSAuthCore.xcscheme - - isShown - - orderHint - 0 - - AWSAuthUI-AWSAuthUI.xcscheme - - isShown - - orderHint - 2 - - AWSAuthUI.xcscheme - - isShown - - orderHint - 1 - - AWSAutoScaling.xcscheme - - isShown - - orderHint - 3 - - AWSCloudWatch.xcscheme - - isShown - - orderHint - 4 - - AWSCognito.xcscheme - - isShown - - orderHint - 5 - - AWSCognitoIdentityProvider.xcscheme - - isShown - - orderHint - 6 - - AWSCognitoIdentityProviderASF.xcscheme - - isShown - - orderHint - 7 - - AWSCore.xcscheme - - isShown - - orderHint - 8 - - AWSDynamoDB.xcscheme - - isShown - - orderHint - 9 - - AWSEC2.xcscheme - - isShown - - orderHint - 10 - - AWSElasticLoadBalancing.xcscheme - - isShown - - orderHint - 11 - - AWSIoT.xcscheme - - isShown - - orderHint - 12 - - AWSKinesis.xcscheme - - isShown - - orderHint - 13 - - AWSLambda.xcscheme - - isShown - - orderHint - 14 - - AWSMachineLearning.xcscheme - - isShown - - orderHint - 15 - - AWSMobileAnalytics.xcscheme - - isShown - - orderHint - 16 - - AWSMobileClient.xcscheme - - isShown - - orderHint - 17 - - AWSS3.xcscheme - - isShown - - orderHint - 18 - - AWSSES.xcscheme - - isShown - - orderHint - 19 - - AWSSNS.xcscheme - - isShown - - orderHint - 21 - - AWSSQS.xcscheme - - isShown - - orderHint - 22 - - AWSSimpleDB.xcscheme - - isShown - - orderHint - 20 - - AWSUserPoolsSignIn-AWSUserPoolsSignIn.xcscheme - - isShown - - orderHint - 24 - - AWSUserPoolsSignIn.xcscheme - - isShown - - orderHint - 23 - - JTAppleCalendar.xcscheme - - isShown - - orderHint - 25 - - Pods-DoctorsNote-DoctorsNoteUITests.xcscheme - - isShown - - orderHint - 27 - - Pods-DoctorsNote.xcscheme - - isShown - - orderHint - 26 - - Pods-DoctorsNoteTests.xcscheme - - isShown - - orderHint - 28 - - PopupKit.xcscheme - - isShown - - orderHint - 29 - - - SuppressBuildableAutocreation - - - From 7243b52c2e30a25dc6a0dfc2c58ec6c965cb640b Mon Sep 17 00:00:00 2001 From: Ariana Zhu Date: Sun, 12 Apr 2020 17:24:44 -0700 Subject: [PATCH 06/24] Added appointment deletion, calendar events --- .../Profile/AppointmentListVC.swift | 47 ++++++++++++++++++- .../DoctorsNote/Profile/CalendarVC.swift | 17 +++++++ .../DoctorsNote/Profile/RemindersVC.swift | 16 ------- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift b/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift index 5ac29679..2997c562 100644 --- a/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift +++ b/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift @@ -19,6 +19,7 @@ class AppointmentListVC: UITableViewController, EKEventEditViewDelegate { @IBOutlet var appointmentTableView: UITableView! + var processor: ConnectionProcessor? override func viewDidLoad() { super.viewDidLoad() @@ -26,9 +27,9 @@ class AppointmentListVC: UITableViewController, EKEventEditViewDelegate { var connector = Connector() AWSMobileClient.default().getTokens(connector.setToken(potentialTokens:potentialError:)) - let processor = ConnectionProcessor(connector: connector) + processor = ConnectionProcessor(connector: connector) do { - appointmentList = try processor.processAppointments(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentlist") + appointmentList = try processor!.processAppointments(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentlist") } catch let error { print((error as! ConnectionError).getMessage()) } @@ -66,6 +67,48 @@ class AppointmentListVC: UITableViewController, EKEventEditViewDelegate { return cell } + // Delete appointment swipe right + // Inspired by https://developerslogblog.wordpress.com/2017/06/28/ios-11-swipe-leftright-in-uitableviewcell/ + // and https://www.hackingwithswift.com/example-code/uikit/how-to-swipe-to-delete-uitableviewcells + override func tableView(_ tableView: UITableView, + leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let appointment = appointmentList[indexPath.row] + + let deleteAction = UIContextualAction(style: .normal, title: "Delete", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in + do { + // TODO: fix url + try self.processor?.processDeleteAppointment(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentdelete", appointment: appointment) + + } catch let error { + print((error as! ConnectionError).getMessage()) + } + + appointmentList.remove(at: indexPath.row) + self.appointmentTableView.deleteRows(at: [indexPath], with: .fade) + self.appointmentTableView.reloadData() + success(true) + }) + deleteAction.backgroundColor = .red + + return UISwipeActionsConfiguration(actions: [deleteAction]) + + } + + // Delete appointment +// override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { +// let appointment = appointmentList[indexPath.row] +// +// do { +// try processor?.processDeleteAppointment(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentdelete", appointment: appointment) +// +// } catch let error { +// print((error as! ConnectionError).getMessage()) +// } +// +// appointmentList.remove(at: indexPath.row) +// appointmentTableView.reloadData() +// } + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 150 } diff --git a/DoctorsNote/DoctorsNote/Profile/CalendarVC.swift b/DoctorsNote/DoctorsNote/Profile/CalendarVC.swift index 0c8266a9..3f295ad6 100644 --- a/DoctorsNote/DoctorsNote/Profile/CalendarVC.swift +++ b/DoctorsNote/DoctorsNote/Profile/CalendarVC.swift @@ -14,6 +14,12 @@ class CalendarVC: UIViewController, JTAppleCalendarViewDataSource, JTAppleCalend @IBOutlet var calendarView: JTAppleCalendarView! + var eventDateFormatter: DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "dd-MMM-yyyy" + return formatter + } + override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Calendar" @@ -57,6 +63,7 @@ class CalendarVC: UIViewController, JTAppleCalendarViewDataSource, JTAppleCalend cell.dateLabel.text = cellState.text handleCellTextColor(cell: cell, cellState: cellState) handleCellSelected(cell: cell, cellState: cellState) + handleCellEvents(cell: cell, cellState: cellState) // optional hide month if cellState.dateBelongsTo == .thisMonth { @@ -66,6 +73,16 @@ class CalendarVC: UIViewController, JTAppleCalendarViewDataSource, JTAppleCalend } } + func handleCellEvents(cell: DateCell, cellState: CellState) { +// let dateString = eventDateFormatter.string(from: cellState.date) + for appointment in appointmentList { + let diff = Calendar.current.dateComponents([.day], from: appointment.getTimeScheduled(), to: cellState.date) + if diff.day == 0 { + cell.dateLabel.textColor = UIColor.red + } + } + } + func handleCellSelected(cell: DateCell, cellState: CellState) { if cellState.isSelected { cell.selectedDateView.layer.cornerRadius = 13 diff --git a/DoctorsNote/DoctorsNote/Profile/RemindersVC.swift b/DoctorsNote/DoctorsNote/Profile/RemindersVC.swift index 1e09f0b6..ac760b11 100644 --- a/DoctorsNote/DoctorsNote/Profile/RemindersVC.swift +++ b/DoctorsNote/DoctorsNote/Profile/RemindersVC.swift @@ -105,22 +105,6 @@ class RemindersVC: UIViewController, UITableViewDelegate, UITableViewDataSource, print() return cell - - /// -// var cell = tableView.dequeueReusableCell(withIdentifier: "remindersCell") -// if cell == nil { -// cell = UITableViewCell(style: .subtitle, reuseIdentifier: "remindersCell") -// } -// -// cell!.textLabel?.text = remindersList[indexPath.row].reminder -// let frequency = "\(remindersList[indexPath.row].numTimesADay ?? "nil") time(s) a day, every \(remindersList[indexPath.row].everyNumDays ?? "nil") day(s)" -// cell!.detailTextLabel?.text = frequency -// -// return cell! - -// let cell = UITableViewCell(style: .default, reuseIdentifier: "remindersCell") -// cell.textLabel?.text = remindersList[indexPath.row].reminder -// return cell } // Delete a reminder From 5433b4b44f55b97bc0f45da3342ea2958f78d6f9 Mon Sep 17 00:00:00 2001 From: Ariana Zhu Date: Sun, 12 Apr 2020 18:27:25 -0700 Subject: [PATCH 07/24] Changed delete appointment processor --- .../Profile/AppointmentListVC.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift b/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift index 2997c562..9657c61a 100644 --- a/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift +++ b/DoctorsNote/DoctorsNote/Profile/AppointmentListVC.swift @@ -41,13 +41,19 @@ class AppointmentListVC: UITableViewController, EKEventEditViewDelegate { // Reload appointment list data var connector = Connector() AWSMobileClient.default().getTokens(connector.setToken(potentialTokens:potentialError:)) - let processor = ConnectionProcessor(connector: connector) + processor = ConnectionProcessor(connector: connector) do { - appointmentList = try processor.processAppointments(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentlist") + appointmentList = try processor!.processAppointments(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentlist") } catch let error { print((error as! ConnectionError).getMessage()) } appointmentTableView.reloadData() + +// print("Appointments: ") +// for appointment in appointmentList { +// print(appointment.getContent()) +// } +// print("End appointments") } override func numberOfSections(in tableView: UITableView) -> Int { @@ -76,8 +82,8 @@ class AppointmentListVC: UITableViewController, EKEventEditViewDelegate { let deleteAction = UIContextualAction(style: .normal, title: "Delete", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in do { - // TODO: fix url - try self.processor?.processDeleteAppointment(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentdelete", appointment: appointment) + // TODO: processor doesn't seem to be deleting + try self.processor!.processDeleteAppointment(url: "https://o2lufnhpee.execute-api.us-east-2.amazonaws.com/Development/appointmentdelete", appointment: appointment) } catch let error { print((error as! ConnectionError).getMessage()) @@ -86,6 +92,10 @@ class AppointmentListVC: UITableViewController, EKEventEditViewDelegate { appointmentList.remove(at: indexPath.row) self.appointmentTableView.deleteRows(at: [indexPath], with: .fade) self.appointmentTableView.reloadData() + print("deleted appointment") + for appointment in appointmentList { + print(appointment.getContent()) + } success(true) }) deleteAction.backgroundColor = .red From 3c0b52647f1c63d4c4cdf8a99a213c5226eb9bd6 Mon Sep 17 00:00:00 2001 From: Ariana Zhu Date: Sun, 12 Apr 2020 18:30:06 -0700 Subject: [PATCH 08/24] Added security questions to login view --- DoctorsNote/DoctorsNote/Login.storyboard | 69 +++++++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/DoctorsNote/DoctorsNote/Login.storyboard b/DoctorsNote/DoctorsNote/Login.storyboard index b7c92ed8..f5192ef7 100644 --- a/DoctorsNote/DoctorsNote/Login.storyboard +++ b/DoctorsNote/DoctorsNote/Login.storyboard @@ -744,6 +744,28 @@ + + + + + + + + + + + + + @@ -752,6 +774,7 @@ + @@ -760,6 +783,7 @@ + @@ -780,21 +804,26 @@ + + + + + @@ -895,7 +924,7 @@ - + @@ -1064,7 +1093,7 @@ - + @@ -1083,7 +1112,7 @@ - + @@ -1139,7 +1168,7 @@ - + @@ -1157,8 +1186,27 @@ + + + + + + + + + +