Created
April 21, 2016 22:34
-
-
Save benjaminsnorris/e7bc0d05d21c0a1f45563f374d417d63 to your computer and use it in GitHub Desktop.
Keychain Wrapper via Dave DeLong
This file contains 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
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