diff --git a/Sources/ReplicantSwift/Models/ReplicantClientModel.swift b/Sources/ReplicantSwift/Models/ReplicantClientModel.swift new file mode 100644 index 0000000..d096ef0 --- /dev/null +++ b/Sources/ReplicantSwift/Models/ReplicantClientModel.swift @@ -0,0 +1,35 @@ +import Foundation +import Security +import CommonCrypto + +public struct ReplicantClientModel +{ + public let polish: PolishClientModel + public var config: ReplicantConfig + public var toneBurst: ToneBurst? + + public init?(withConfig config: ReplicantConfig) + { + guard let polish = PolishClientModel(serverPublicKeyData: config.serverPublicKey) + else + { + return nil + } + + if let addSequences = config.addSequences, let removeSequences = config.removeSequences + { + self.toneBurst = ToneBurst(addSequences: addSequences, removeSequences: removeSequences) + } + + self.config = config + self.polish = polish + } +} + +extension Data +{ + public var bytes: Array + { + return Array(self) + } +} diff --git a/Sources/ReplicantSwift/Models/ReplicantServerModel.swift b/Sources/ReplicantSwift/Models/ReplicantServerModel.swift new file mode 100644 index 0000000..33736b1 --- /dev/null +++ b/Sources/ReplicantSwift/Models/ReplicantServerModel.swift @@ -0,0 +1,32 @@ +// +// ReplicantServerModel.swift +// ReplicantSwift +// +// Created by Adelita Schule on 12/18/18. +// + +import Foundation + +public struct ReplicantServerModel +{ + public var polish: PolishServerModel + public var config: ReplicantServerConfig + public var toneBurst: ToneBurst? + + public init?(withConfig config: ReplicantServerConfig) + { + guard let polish = PolishServerModel() + else + { + return nil + } + + if let addSequences = config.addSequences, let removeSequences = config.removeSequences + { + self.toneBurst = ToneBurst(addSequences: addSequences, removeSequences: removeSequences) + } + + self.config = config + self.polish = polish + } +} diff --git a/Sources/ReplicantSwift/Polish/PolishClientModel.swift b/Sources/ReplicantSwift/Polish/PolishClientModel.swift new file mode 100644 index 0000000..92e96f8 --- /dev/null +++ b/Sources/ReplicantSwift/Polish/PolishClientModel.swift @@ -0,0 +1,43 @@ +// +// PolishClientModel.swift +// ReplicantSwift +// +// Created by Adelita Schule on 12/18/18. +// + +import Foundation + +public class PolishClientModel +{ + let controller = PolishController() + + public var serverPublicKey: SecKey + public var publicKey: SecKey + public var privateKey: SecKey + + public init?(serverPublicKeyData: Data) + { + controller.deleteKeys() + + guard let sPublicKey = controller.decodeKey(fromData: serverPublicKeyData) + else + { + return nil + } + + guard let newKeyPair = controller.generateKeyPair() + else + { + return nil + } + + self.serverPublicKey = sPublicKey + self.privateKey = newKeyPair.privateKey + self.publicKey = newKeyPair.publicKey + } + + deinit + { + controller.deleteKeys() + } +} diff --git a/Sources/ReplicantSwift/Polish.swift b/Sources/ReplicantSwift/Polish/PolishController.swift similarity index 73% rename from Sources/ReplicantSwift/Polish.swift rename to Sources/ReplicantSwift/Polish/PolishController.swift index cb44590..3eee5bd 100644 --- a/Sources/ReplicantSwift/Polish.swift +++ b/Sources/ReplicantSwift/Polish/PolishController.swift @@ -1,75 +1,93 @@ // -// Polish.swift +// PolishController.swift // ReplicantSwift // -// Created by Adelita Schule on 11/9/18. +// Created by Adelita Schule on 12/18/18. // import Foundation -import Security -import CommonCrypto public let keySize = 64 public let aesOverheadSize = 81 -public class Polish: NSObject +public struct PolishController { - static let clientTag = "org.operatorfoundation.replicant.client".data(using: .utf8)! let algorithm: SecKeyAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM + let polishTag = "org.operatorfoundation.replicant.polish".data(using: .utf8)! - public var recipientPublicKey: SecKey? - public var publicKey: SecKey - public var privateKey: SecKey - - public init?(recipientPublicKeyData: Data?) + /// Decode data to get public key. This only decodes key data that is NOT padded. + public func decodeKey(fromData publicKeyData: Data) -> SecKey? { - Polish.deleteKeys() + var error: Unmanaged? - if let rPublicKeyData = recipientPublicKeyData - { - recipientPublicKey = Polish.decodeKey(fromData: rPublicKeyData) - } + let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeyClass as String: kSecAttrKeyClassPublic, + kSecAttrKeySizeInBits as String: 256] - guard let newKeyPair = Polish.generateKeyPair() - else + guard let decodedPublicKey = SecKeyCreateWithData(publicKeyData as CFData, options as CFDictionary, &error) + else { + print("\nUnable to decode server public key: \(error!.takeRetainedValue() as Error)\n") return nil } - self.privateKey = newKeyPair.privateKey - self.publicKey = newKeyPair.publicKey + return decodedPublicKey } - deinit + public func generatePrivateKey() -> SecKey? { - Polish.deleteKeys() + // Generate private key + var error: Unmanaged? + let attributes = self.generateKeyAttributesDictionary() + + guard let privateKey = SecKeyCreateRandomKey(attributes, &error) + else + { + print("\nUnable to generate the client private key: \(error!.takeRetainedValue() as Error)\n") + return nil + } + + return privateKey } - static func deleteKeys() + func generatePublicKey(usingPrivateKey privateKey: SecKey) -> SecKey? { - //Remove client keys from secure enclave - let query = Polish.generateKeyAttributesDictionary() - let deleteStatus = SecItemDelete(query) - print("\nAttempted to delete key from secure enclave. Status: \(deleteStatus)\n") + guard let publicKey = SecKeyCopyPublicKey(privateKey) + else + { + print("\nUnable to generate a public key from the provided private key.\n") + return nil + } + + return publicKey } - public static func generatePrivateKey() -> SecKey? + func generateKeyPair() -> (privateKey: SecKey, publicKey: SecKey)? { - // Generate private key - var error: Unmanaged? - let attributes = Polish.generateKeyAttributesDictionary() + guard let privateKey = generatePrivateKey() + else + { + return nil + } - guard let alicePrivate = SecKeyCreateRandomKey(attributes, &error) + guard let publicKey = generatePublicKey(usingPrivateKey: privateKey) else { - print("\nUnable to generate the client private key: \(error!.takeRetainedValue() as Error)\n") return nil } - return alicePrivate + return (privateKey, publicKey) + } + + func deleteKeys() + { + //Remove client keys from secure enclave + let query = generateKeyAttributesDictionary() + let deleteStatus = SecItemDelete(query) + print("\nAttempted to delete key from secure enclave. Status: \(deleteStatus)\n") } - static func generateKeyAttributesDictionary() -> CFDictionary + func generateKeyAttributesDictionary() -> CFDictionary { let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAlwaysThisDeviceOnly, @@ -78,7 +96,7 @@ public class Polish: NSObject let privateKeyAttributes: [String: Any] = [ kSecAttrIsPermanent as String: true, - kSecAttrApplicationTag as String: Polish.clientTag, + kSecAttrApplicationTag as String: polishTag, //kSecAttrAccessControl as String: access ] @@ -92,35 +110,6 @@ public class Polish: NSObject return attributes as CFDictionary } - static func generatePublicKey(usingPrivateKey privateKey: SecKey) -> SecKey? - { - guard let alicePublic = SecKeyCopyPublicKey(privateKey) - else - { - print("\nUnable to generate a public key from the provided private key.\n") - return nil - } - - return alicePublic - } - - static func generateKeyPair() -> (privateKey: SecKey, publicKey: SecKey)? - { - guard let privateKey = generatePrivateKey() - else - { - return nil - } - - guard let publicKey = generatePublicKey(usingPrivateKey: privateKey) - else - { - return nil - } - - return (privateKey, publicKey) - } - /// This is the format needed to send the key to the server. public func generateAndEncryptPaddedKeyData(fromKey key: SecKey, withChunkSize chunkSize: Int, usingServerKey serverKey: SecKey) -> Data? { @@ -145,7 +134,7 @@ public class Polish: NSObject // Encrypt the key guard let encryptedKeyData = encrypt(payload: newKeyData, usingPublicKey: serverKey) - else + else { return nil } @@ -153,24 +142,7 @@ public class Polish: NSObject return encryptedKeyData } - /// Decode data to get public key. This only decodes key data that is NOT padded. - public static func decodeKey(fromData publicKeyData: Data) -> SecKey? - { - var error: Unmanaged? - - let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, - kSecAttrKeyClass as String: kSecAttrKeyClassPublic, - kSecAttrKeySizeInBits as String: 256] - - guard let decodedBobPublicKey = SecKeyCreateWithData(publicKeyData as CFData, options as CFDictionary, &error) - else - { - print("\nUnable to decode server public key: \(error!.takeRetainedValue() as Error)\n") - return nil - } - - return decodedBobPublicKey - } + //MARK: Encryption /// Encrypt payload public func encrypt(payload: Data, usingPublicKey publicKey: SecKey) -> Data? @@ -218,4 +190,3 @@ public class Polish: NSObject } } } - diff --git a/Sources/ReplicantSwift/Polish/PolishServerModel.swift b/Sources/ReplicantSwift/Polish/PolishServerModel.swift new file mode 100644 index 0000000..c8a917b --- /dev/null +++ b/Sources/ReplicantSwift/Polish/PolishServerModel.swift @@ -0,0 +1,36 @@ +// +// PolishServerModel.swift +// ReplicantSwift +// +// Created by Adelita Schule on 12/18/18. +// + +import Foundation + +public class PolishServerModel +{ + let controller = PolishController() + + public var publicKey: SecKey + public var privateKey: SecKey + + + public init?() + { + controller.deleteKeys() + + guard let newKeyPair = controller.generateKeyPair() + else + { + return nil + } + + self.privateKey = newKeyPair.privateKey + self.publicKey = newKeyPair.publicKey + } + + deinit + { + controller.deleteKeys() + } +} diff --git a/Sources/ReplicantSwift/ReplicantConfig.swift b/Sources/ReplicantSwift/ReplicantConfig.swift index d02c09c..13c61b2 100644 --- a/Sources/ReplicantSwift/ReplicantConfig.swift +++ b/Sources/ReplicantSwift/ReplicantConfig.swift @@ -15,7 +15,6 @@ public struct ReplicantConfig: Codable public var addSequences: [SequenceModel]? public var removeSequences: [SequenceModel]? - public init?(serverPublicKey: Data, chunkSize: Int, chunkTimeout: Int, addSequences: [SequenceModel]?, removeSequences: [SequenceModel]?) { guard chunkSize >= keySize + aesOverheadSize @@ -31,7 +30,8 @@ public struct ReplicantConfig: Codable self.removeSequences = removeSequences } - public func createJSON() -> String? + /// Creates and returns a JSON representation of the ReplicantConfig struct. + public func createJSON() -> Data? { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -39,7 +39,7 @@ public struct ReplicantConfig: Codable do { let configData = try encoder.encode(self) - return String(data: configData, encoding: .utf8) + return configData } catch (let error) { @@ -48,6 +48,10 @@ public struct ReplicantConfig: Codable } } + /// Checks for a valid JSON at the provided path and attempts to decode it into a Replicant client configuration file. Returns a ReplicantConfig struct if it is successful + /// - Parameters: + /// - path: The complete path where the config file is located. + /// - Returns: The ReplicantConfig struct that was decoded from the JSON file located at the provided path, or nil if the file was invalid or missing. static public func parseJSON(atPath path: String) -> ReplicantConfig? { let fileManager = FileManager() diff --git a/Sources/ReplicantSwift/ReplicantConfigTemplate.swift b/Sources/ReplicantSwift/ReplicantConfigTemplate.swift index 0895ebf..ac02a23 100644 --- a/Sources/ReplicantSwift/ReplicantConfigTemplate.swift +++ b/Sources/ReplicantSwift/ReplicantConfigTemplate.swift @@ -29,7 +29,7 @@ public struct ReplicantConfigTemplate: Codable self.removeSequences = removeSequences } - public func createJSON() -> String? + public func createJSON() -> Data? { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -37,7 +37,7 @@ public struct ReplicantConfigTemplate: Codable do { let configData = try encoder.encode(self) - return String(data: configData, encoding: .utf8) + return configData } catch (let error) { @@ -70,24 +70,39 @@ public struct ReplicantConfigTemplate: Codable } } - public func createConfig(withServerKey serverPublicKey: SecKey) -> String? + /// Creates a Replicant client configuration file at the specified path. + /// + /// - Parameters: + /// - path: The filepath where the new config file should be saved, this should included the desired file name. + /// - serverPublicKey: The public key for the Replicant server. This is required in order for the client to be able to communicate with the server. + /// - Returns: A boolean indicating whether or not the config was created successfully + public func createConfig(atPath path: String, withServerKey serverPublicKey: SecKey) -> Bool { - // Encode key as data + let fileManager = FileManager() var error: Unmanaged? + // Encode key as data guard let keyData = SecKeyCopyExternalRepresentation(serverPublicKey, &error) as Data? else { print("\nUnable to generate public key external representation: \(error!.takeRetainedValue() as Error)\n") - return nil + return false } guard let replicantConfig = ReplicantConfig(serverPublicKey: keyData, chunkSize: self.chunkSize, chunkTimeout: self.chunkTimeout, addSequences: self.addSequences, removeSequences: self.removeSequences) else { - return nil + return false } - return replicantConfig.createJSON() + guard let jsonData = replicantConfig.createJSON() + else + { + return false + } + + let configCreated = fileManager.createFile(atPath: path, contents: jsonData) + + return configCreated } } diff --git a/Sources/ReplicantSwift/ReplicantServerConfig.swift b/Sources/ReplicantSwift/ReplicantServerConfig.swift index 2604d9e..4b87c67 100644 --- a/Sources/ReplicantSwift/ReplicantServerConfig.swift +++ b/Sources/ReplicantSwift/ReplicantServerConfig.swift @@ -28,7 +28,8 @@ public struct ReplicantServerConfig: Codable self.removeSequences = removeSequences } - public func createJSON() -> String? + /// Creates and returns a JSON representation of the ReplicantServerConfig struct. + public func createJSON() -> Data? { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -36,7 +37,7 @@ public struct ReplicantServerConfig: Codable do { let serverConfigData = try encoder.encode(self) - return String(data: serverConfigData, encoding: .utf8) + return serverConfigData } catch (let error) { @@ -45,6 +46,10 @@ public struct ReplicantServerConfig: Codable } } + /// Checks for a valid JSON at the provided path and attempts to decode it into a Replicant server configuration file. Returns a ReplicantConfig struct if it is successful + /// - Parameters: + /// - path: The complete path where the config file is located. + /// - Returns: The ReplicantServerConfig struct that was decoded from the JSON file located at the provided path, or nil if the file was invalid or missing. static public func parseJSON(atPath path: String) -> ReplicantServerConfig? { let filemanager = FileManager() diff --git a/Sources/ReplicantSwift/ReplicantSwift.swift b/Sources/ReplicantSwift/ReplicantSwift.swift deleted file mode 100644 index e794f91..0000000 --- a/Sources/ReplicantSwift/ReplicantSwift.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation -import Security -import CommonCrypto - -public struct Replicant -{ - public let polish: Polish - public var config: ReplicantConfig - public var toneBurst: ToneBurst? - - public init?(withConfig config: ReplicantConfig) - { - guard let polish = Polish(recipientPublicKeyData: config.serverPublicKey) - else - { - return nil - } - - if let addSequences = config.addSequences, let removeSequences = config.removeSequences - { - self.toneBurst = ToneBurst(addSequences: addSequences, removeSequences: removeSequences) - } - - self.config = config - self.polish = polish - } -} - -public struct ReplicantServer -{ - public var polish: Polish - public var config: ReplicantServerConfig - public var toneBurst: ToneBurst? - - public init?(withConfig config: ReplicantServerConfig) - { - guard let polish = Polish(recipientPublicKeyData: nil) - else - { - return nil - } - - if let addSequences = config.addSequences, let removeSequences = config.removeSequences - { - self.toneBurst = ToneBurst(addSequences: addSequences, removeSequences: removeSequences) - } - - self.config = config - self.polish = polish - } -} - -extension Data -{ - public var bytes: Array - { - return Array(self) - } -}