Created
November 16, 2016 15:16
-
-
Save khanlou/4d1736145b496f4e4298cf4dcd08d35e to your computer and use it in GitHub Desktop.
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
import Foundation | |
import Security | |
private let DataKey = "MyDataKey" | |
/** | |
* The KeychainResult class wraps an OSStatus that is returned from a keychain action. It vends KeychainErrors. | |
*/ | |
struct KeychainResult { | |
let keychainError: OSStatus | |
var isSuccess: Bool { | |
return keychainError == errSecSuccess | |
} | |
//An instance of KeychainError describing the error if there is one. | |
var error: KeychainError? { | |
guard !isSuccess else { return nil } | |
return KeychainError(error: keychainError) | |
} | |
} | |
/** | |
An enum describing all of the errors that can result from a keychain action. | |
- FunctionNotImplemented: Function or operation not implemented. | |
- InvalidParameters: One or more parameters passed to a function were not valid. | |
- MemoryAllocationError: Failed to allocate memory. | |
- KeychainNotAvailable: No keychain is available. You may need to restart your computer. | |
- DuplicateItem: The specified item already exists in the keychain. | |
- ItemNotFound: The specified item could not be found in the keychain. | |
- InteractionNotAllowed: User interaction is not allowed. | |
- DecodingError: Unable to decode the provided data. | |
- AuthenticationFailed: The user name or passphrase you entered is not correct. | |
*/ | |
enum KeychainError: ErrorType { | |
case FunctionNotImplemented | |
case InvalidParameters | |
case MemoryAllocationError | |
case KeychainNotAvailable | |
case DuplicateItem | |
case ItemNotFound | |
case InteractionNotAllowed | |
case DecodingError | |
case AuthenticationFailed | |
init?(error: OSStatus) { | |
switch error { | |
case errSecUnimplemented: | |
self = .FunctionNotImplemented | |
case errSecParam: | |
self = .InvalidParameters | |
case errSecAllocate: | |
self = .MemoryAllocationError | |
case errSecNotAvailable: | |
self = .KeychainNotAvailable | |
case errSecDuplicateItem: | |
self = .DuplicateItem | |
case errSecItemNotFound: | |
self = .ItemNotFound | |
case errSecInteractionNotAllowed: | |
self = .InteractionNotAllowed | |
case errSecDecode: | |
self = .DecodingError | |
case errSecAuthFailed: | |
self = .AuthenticationFailed | |
default: | |
return nil | |
} | |
} | |
/// A description of the error. Not localized. | |
var errorDescription: String { | |
switch self { | |
case .FunctionNotImplemented: | |
return "Function or operation not implemented." | |
case .InvalidParameters: | |
return "One or more parameters passed to a function were not valid." | |
case .MemoryAllocationError: | |
return "Failed to allocate memory." | |
case .KeychainNotAvailable: | |
return "No keychain is available. You may need to restart your computer." | |
case .DuplicateItem: | |
return "The specified item already exists in the keychain." | |
case .ItemNotFound: | |
return "The specified item could not be found in the keychain." | |
case .InteractionNotAllowed: | |
return "User interaction is not allowed." | |
case .DecodingError: | |
return "Unable to decode the provided data." | |
case .AuthenticationFailed: | |
return "The user name or passphrase you entered is not correct." | |
} | |
} | |
} | |
/// A class that conforms to ObjectStorageProtocol and provides access into the keychain. | |
class KeychainStorage<T: NSCoding>: ObjectStorageProtocol { | |
typealias KeychainQuery = [String: AnyObject] | |
let name: String | |
/** | |
Constructs a new KeychainStorage. The name of the keychain storage should be unique and will define where the object is stored. | |
- parameter name: The name of the storage. This name will define where the object is stored. | |
- returns: An intialized KeychainStorage. | |
*/ | |
init(name: String) { | |
self.name = name | |
} | |
func saveObject(object: T) { | |
let attributes: KeychainQuery = [ | |
data: NSKeyedArchiver.archivedDataWithRootObject(object) | |
] | |
if let _ = fetchObject() { | |
updateObject(attributes) | |
} else { | |
createNewObject(attributes) | |
} | |
} | |
private func createNewObject(attributes: KeychainQuery) { | |
var query = newQuery() | |
query.merge(attributes) | |
let status = SecItemAdd(query, nil) | |
let result = KeychainResult(keychainError: status) | |
if let error = result.error { | |
print(error) | |
} | |
} | |
private func updateObject(attributes: KeychainQuery) { | |
let status = SecItemUpdate(newQuery(), attributes) | |
let result = KeychainResult(keychainError: status) | |
if let error = result.error { | |
print(error) | |
} | |
} | |
func fetchObject() -> T? { | |
var result: AnyObject? | |
let status = withUnsafeMutablePointer(&result) { | |
SecItemCopyMatching(newSearchQuery(), UnsafeMutablePointer($0)) | |
} | |
let keychainResult = KeychainResult(keychainError: status) | |
if let error = keychainResult.error { | |
print(error) | |
} | |
return result.flatMap({ result in | |
return result as? NSData | |
}).flatMap({ data in | |
return NSKeyedUnarchiver.unarchiveObjectWithData(data) | |
}).flatMap({ object in | |
return object as? T | |
}) | |
} | |
func deleteObject() { | |
let status = SecItemDelete(newQuery()) | |
let result = KeychainResult(keychainError: status) | |
if let error = result.error { | |
print(error) | |
} | |
} | |
private func newSearchQuery() -> KeychainQuery { | |
var query = newQuery() | |
query[shouldReturnData] = true | |
query[matchLimit] = matchOnlyOne | |
return query | |
} | |
private func newQuery() -> KeychainQuery { | |
return [ | |
securityClass: kSecClassGenericPassword, | |
attributeLabel: name, | |
serviceName: DataKey | |
] | |
} | |
//MARK: - Constants | |
private let securityClass = kSecClass as String | |
private let attributeLabel = kSecAttrLabel as String | |
private let serviceName = kSecAttrService as String | |
private let shouldReturnData = kSecReturnData as String | |
private let matchLimit = kSecMatchLimit as String | |
private let matchOnlyOne = kSecMatchLimitOne as String | |
private let data = kSecValueData as String | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment