Skip to content

Instantly share code, notes, and snippets.

@piotrbernad
Last active November 14, 2017 09:34
Show Gist options
  • Save piotrbernad/48206f122896ebead4981677b63ac14c to your computer and use it in GitHub Desktop.
Save piotrbernad/48206f122896ebead4981677b63ac14c to your computer and use it in GitHub Desktop.
KeychainAccessor
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