Skip to content

Instantly share code, notes, and snippets.

@benjaminsnorris
Created April 21, 2016 22:34
Show Gist options
  • Save benjaminsnorris/e7bc0d05d21c0a1f45563f374d417d63 to your computer and use it in GitHub Desktop.
Save benjaminsnorris/e7bc0d05d21c0a1f45563f374d417d63 to your computer and use it in GitHub Desktop.
Keychain Wrapper via Dave DeLong
import Security
/// A wrapper around the Keychain API.
public class Keychain {
private let name: String
private let accessGroup: String?
public init(name: String, accessGroup: String? = nil) {
self.name = name
self.accessGroup = accessGroup
}
/// Get and set values into the Keychain.
public subscript(key: String) -> NSSecureCoding? {
get { return fetch(key) }
set(newValue) { save(newValue, forKey: key) }
}
/// Retrieve all the keys used to store data in the Keychain.
public func allKeys() -> Array<String> {
var query = queryForKey(nil)
query[kSecReturnAttributes as String] = true
var values: NSArray?
let status = withUnsafeMutablePointer(&values) {
return SecItemCopyMatching(query, UnsafeMutablePointer($0))
}
guard status == errSecSuccess else { return [] }
guard let attributes = values as? Array<Dictionary<String, AnyObject>> else { return [] }
let keys = attributes.reduce(Array<String>()) { (soFar, element) in
guard let key = element[kSecAttrAccount as String] as? String else { return soFar }
var aggregate = soFar
aggregate.append(key)
return aggregate
}
return keys
}
/// Retrieve a value from the Keychain, if it exists.
internal func fetch(key: String) -> NSSecureCoding? {
var query = queryForKey(key)
query[kSecReturnData as String] = true
var value: NSData?
let status = withUnsafeMutablePointer(&value) {
return SecItemCopyMatching(query, UnsafeMutablePointer($0))
}
guard status == errSecSuccess else { return nil }
guard let data = value else { return nil }
guard let object = NSKeyedUnarchiver.unarchiveObjectWithData(data) else { return nil }
return object as? NSSecureCoding
}
/// Persist a value to the Keychain. If `value` is nil, then any persisted value is deleted.
internal func save(value: NSSecureCoding?, forKey key: String) {
let query = queryForKey(key)
guard let object = value else {
SecItemDelete(query)
return
}
let data = NSKeyedArchiver.archivedDataWithRootObject(object)
if let _ = fetch(key) {
let attributes = [kSecValueData as String: data]
SecItemUpdate(query, attributes)
} else {
var mutableQuery = query
mutableQuery[kSecValueData as String] = data
mutableQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock as String
// mutableQuery[kSecAttrSynchronizable as String] = kCFBooleanTrue
mutableQuery[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked as String
let _ = SecItemAdd(mutableQuery, nil)
// NSLog("Saving %@: %d", key, status)
}
}
private func queryForKey(key: String?) -> Dictionary<String, AnyObject> {
var q = [kSecClass as String: kSecClassGenericPassword as String,
kSecAttrService as String: name]
if let key = key {
q[kSecAttrAccount as String] = key
}
if let group = accessGroup {
q[kSecAttrAccessGroup as String] = group
}
q[kSecAttrSynchronizable as String] = kSecAttrSynchronizableAny as String
return q
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment