Last active
November 14, 2017 09:34
-
-
Save piotrbernad/48206f122896ebead4981677b63ac14c to your computer and use it in GitHub Desktop.
KeychainAccessor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct KeychainAccessor<T> { | |
| // key name | |
| let key: String | |
| // transformers | |
| let decoding: ((T) throws -> Data?) | |
| let encoding: ((Data) throws -> T?) | |
| // needs public accessor | |
| public init(name: String, decoding: @escaping ((T) throws -> Data?), encoding: @escaping ((Data) throws -> T?)) { | |
| // slot name | |
| self.key = name | |
| // | |
| self.decoding = decoding | |
| self.encoding = encoding | |
| } | |
| } | |
| extension KeychainAccessor { | |
| /// This method saves value in keychain. Before save, value is converted to `Data` and encrypted. | |
| /// | |
| /// - Parameters: | |
| /// - keychain: Defined keychain used to store value in | |
| /// - value: The value that will be stored | |
| func set(keychain: KeychainAccess, value: T?) throws { | |
| try set(keychain: keychain, value: value) { | |
| // get cipher | |
| guard let cipher = try self.cipher(keychain: keychain) | |
| else { return $0 } | |
| // encrypt data | |
| return try cipher.encrypt(data: $0) | |
| } | |
| } | |
| /// This method reads value from keychain. Read bytes are decrypted and converted to represent generic type | |
| /// | |
| /// - keychain: Defined keychain used to read value from keychain | |
| /// - Returns: Value stored in keychain | |
| func get(keychain: KeychainAccess) throws -> T? { | |
| return try get(keychain: keychain) { | |
| // get cipher | |
| guard let cipher = try self.cipher(keychain: keychain) | |
| else { return $0 } | |
| // decrypt data | |
| return try cipher.decrypt(data: $0) | |
| } | |
| } | |
| private func set(keychain: KeychainAccess, value: T?, encrypt: (Data) throws -> Data) throws { | |
| guard let value = value | |
| else { try keychain.remove(key); return } | |
| // decode value | |
| guard let decodedData = try decoding(value) | |
| else { return } | |
| // save into keychain | |
| try keychain.set(encrypt(decodedData), key: key) | |
| } | |
| private func get(keychain: KeychainAccess, decrypt: (Data) throws -> Data) throws -> T? { | |
| // get from keychain | |
| guard let data = try keychain.get(key) | |
| else { return nil } | |
| // encode data | |
| return try encoding(decrypt(data)) | |
| } | |
| // cipher block | |
| private func cipher(keychain: KeychainAccess) throws -> Cipher? { | |
| let slot = KeychainAccessor<Data>(name: "com.appunite.keychain.token", | |
| decoding: { $0 }, | |
| encoding: { $0 } | |
| ) | |
| KeychainAccessorQueue.queue.sync { | |
| // get cipher token | |
| let token = try slot.get(keychain: keychain) { | |
| return try Cipher.root.decrypt(data: $0) | |
| } | |
| // | |
| if let token = token { | |
| return .generated(token) | |
| } | |
| // generate new random key with defined length | |
| var newValue = Data(count: 32) | |
| _ = newValue.withUnsafeMutableBytes { | |
| SecRandomCopyBytes(kSecRandomDefault, newValue.count, $0) | |
| } | |
| // save new cipher | |
| try slot.set(keychain: keychain, value: newValue) { | |
| return try Cipher.root.encrypt(data: $0) | |
| } | |
| // | |
| return .generated(newValue) | |
| } | |
| } | |
| } | |
| private class KeychainAccessorQueue { | |
| static let queue: DispatchQueue = DispatchQueue(label: "com.appunite.keychain-accessor.queue") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment