vendoo_v1.0/Pods/Locksmith/Source/Locksmith.swift

630 lines
23 KiB
Swift

import Foundation
public let LocksmithDefaultService = NSBundle.mainBundle().infoDictionary![String(kCFBundleIdentifierKey)] as? String ?? "com.locksmith.defaultService"
public typealias PerformRequestClosureType = (requestReference: CFDictionaryRef, inout result: AnyObject?) -> (OSStatus)
// MARK: - Locksmith
public struct Locksmith {
public static func loadDataForUserAccount(userAccount: String, inService service: String = LocksmithDefaultService) -> [String: AnyObject]? {
struct ReadRequest: GenericPasswordSecureStorable, ReadableSecureStorable {
let service: String
let account: String
}
let request = ReadRequest(service: service, account: userAccount)
return request.readFromSecureStore()?.data
}
public static func saveData(data: [String: AnyObject], forUserAccount userAccount: String, inService service: String = LocksmithDefaultService) throws {
struct CreateRequest: GenericPasswordSecureStorable, CreateableSecureStorable {
let service: String
let account: String
let data: [String: AnyObject]
}
let request = CreateRequest(service: service, account: userAccount, data: data)
return try request.createInSecureStore()
}
public static func deleteDataForUserAccount(userAccount: String, inService service: String = LocksmithDefaultService) throws {
struct DeleteRequest: GenericPasswordSecureStorable, DeleteableSecureStorable {
let service: String
let account: String
}
let request = DeleteRequest(service: service, account: userAccount)
return try request.deleteFromSecureStore()
}
public static func updateData(data: [String: AnyObject], forUserAccount userAccount: String, inService service: String = LocksmithDefaultService) throws {
struct UpdateRequest: GenericPasswordSecureStorable, CreateableSecureStorable {
let service: String
let account: String
let data: [String: AnyObject]
}
let request = UpdateRequest(service: service, account: userAccount, data: data)
try request.updateInSecureStore()
}
}
// MARK: - SecureStorable
/// The base protocol that indicates conforming types will have the ability to be stored in a secure storage container, such as the iOS keychain.
public protocol SecureStorable {
var accessible: LocksmithAccessibleOption? { get }
var accessGroup: String? { get }
}
public extension SecureStorable {
var accessible: LocksmithAccessibleOption? { return nil }
var accessGroup: String? { return nil }
var secureStorableBaseStoragePropertyDictionary: [String: AnyObject] {
let dictionary = [
String(kSecAttrAccessGroup): accessGroup,
String(kSecAttrAccessible): accessible?.rawValue
]
return Dictionary(withoutOptionalValues: dictionary)
}
private func performSecureStorageAction(closure: PerformRequestClosureType, secureStoragePropertyDictionary: [String: AnyObject]) throws -> [String: AnyObject]? {
var result: AnyObject?
let request = secureStoragePropertyDictionary
let requestReference = request as CFDictionaryRef
let status = closure(requestReference: requestReference, result: &result)
let statusCode = Int(status)
if let error = LocksmithError(fromStatusCode: statusCode) {
throw error
}
// hmmmm... bit leaky
if status != errSecSuccess {
return nil
}
guard let dictionary = result as? NSDictionary else {
return nil
}
if dictionary[String(kSecValueData)] as? NSData == nil {
return nil
}
return result as? [String: AnyObject]
}
}
public extension SecureStorable where Self : InternetPasswordSecureStorable {
private var internetPasswordBaseStoragePropertyDictionary: [String: AnyObject] {
var dictionary = [String: AnyObject]()
// add in whatever turns out to be required...
dictionary[String(kSecAttrServer)] = server
dictionary[String(kSecAttrPort)] = port
dictionary[String(kSecAttrProtocol)] = internetProtocol.rawValue
dictionary[String(kSecAttrAuthenticationType)] = authenticationType.rawValue
dictionary[String(kSecAttrSecurityDomain)] = securityDomain
dictionary[String(kSecAttrPath)] = path
dictionary[String(kSecClass)] = LocksmithSecurityClass.InternetPassword.rawValue
let toMergeWith = [
accountSecureStoragePropertyDictionary,
describableSecureStoragePropertyDictionary,
commentableSecureStoragePropertyDictionary,
creatorDesignatableSecureStoragePropertyDictionary,
typeDesignatableSecureStoragePropertyDictionary,
isInvisibleSecureStoragePropertyDictionary,
isNegativeSecureStoragePropertyDictionary
]
for dict in toMergeWith {
dictionary = Dictionary(initial: dictionary, toMerge: dict)
}
return dictionary
}
}
public protocol AccountBasedSecureStorable {
/// The account that the stored value will belong to
var account: String { get }
}
public extension AccountBasedSecureStorable {
private var accountSecureStoragePropertyDictionary: [String: AnyObject] {
return [String(kSecAttrAccount): account]
}
}
public protocol AccountBasedSecureStorableResultType: AccountBasedSecureStorable, SecureStorableResultType {}
public extension AccountBasedSecureStorableResultType {
var account: String {
return resultDictionary[String(kSecAttrAccount)] as! String
}
}
public protocol DescribableSecureStorable {
/// A description of the item in the secure storage container.
var description: String? { get }
}
public extension DescribableSecureStorable {
var description: String? { return nil }
private var describableSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [
String(kSecAttrDescription): description
])
}
}
public protocol DescribableSecureStorableResultType: DescribableSecureStorable, SecureStorableResultType {}
public extension DescribableSecureStorableResultType {
var description: String? {
return resultDictionary[String(kSecAttrDescription)] as? String
}
}
public protocol CommentableSecureStorable {
/// A comment attached to the item in the secure storage container.
var comment: String? { get }
}
public extension CommentableSecureStorable {
var comment: String? { return nil }
private var commentableSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [
String(kSecAttrComment): comment
])
}
}
public protocol CommentableSecureStorableResultType: CommentableSecureStorable, SecureStorableResultType {}
public extension CommentableSecureStorableResultType {
var comment: String? {
return resultDictionary[String(kSecAttrComment)] as? String
}
}
public protocol CreatorDesignatableSecureStorable {
/// The creator of the item in the secure storage container.
var creator: UInt? { get }
}
public extension CreatorDesignatableSecureStorable {
var creator: UInt? { return nil }
private var creatorDesignatableSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [String(kSecAttrCreator): creator])
}
}
public protocol CreatorDesignatableSecureStorableResultType: CreatorDesignatableSecureStorable, SecureStorableResultType {}
public extension CreatorDesignatableSecureStorableResultType {
var creator: UInt? {
return resultDictionary[String(kSecAttrCreator)] as? UInt
}
}
public protocol LabellableSecureStorable {
/// A label for the item in the secure storage container.
var label: String? { get }
}
public extension LabellableSecureStorable {
var label: String? { return nil }
private var labellableSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [String(kSecAttrLabel): label])
}
}
public protocol LabellableSecureStorableResultType: LabellableSecureStorable, SecureStorableResultType {}
public extension LabellableSecureStorableResultType {
var label: String? {
return resultDictionary[String(kSecAttrLabel)] as? String
}
}
public protocol TypeDesignatableSecureStorable {
/// The type of the stored item
var type: UInt? { get }
}
public extension TypeDesignatableSecureStorable {
var type: UInt? { return nil }
private var typeDesignatableSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [String(kSecAttrType): type])
}
}
public protocol TypeDesignatableSecureStorableResultType: TypeDesignatableSecureStorable, SecureStorableResultType {}
public extension TypeDesignatableSecureStorableResultType {
var type: UInt? {
return resultDictionary[String(kSecAttrType)] as? UInt
}
}
public protocol IsInvisibleAssignableSecureStorable {
var isInvisible: Bool? { get }
}
public extension IsInvisibleAssignableSecureStorable {
var isInvisible: Bool? { return nil }
private var isInvisibleSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [String(kSecAttrIsInvisible): isInvisible])
}
}
public protocol IsInvisibleAssignableSecureStorableResultType: IsInvisibleAssignableSecureStorable, SecureStorableResultType {}
public extension IsInvisibleAssignableSecureStorableResultType {
var isInvisible: Bool? {
return resultDictionary[String(kSecAttrIsInvisible)] as? Bool
}
}
public protocol IsNegativeAssignableSecureStorable {
var isNegative: Bool? { get }
}
public extension IsNegativeAssignableSecureStorable {
var isNegative: Bool? { return nil }
private var isNegativeSecureStoragePropertyDictionary: [String: AnyObject] {
return Dictionary(withoutOptionalValues: [String(kSecAttrIsNegative): isNegative])
}
}
public protocol IsNegativeAssignableSecureStorableResultType: IsNegativeAssignableSecureStorable, SecureStorableResultType {
}
public extension IsNegativeAssignableSecureStorableResultType {
var isNegative: Bool? {
return resultDictionary[String(kSecAttrIsNegative)] as? Bool
}
}
// MARK: - GenericPasswordSecureStorable
/// The protocol that indicates a type conforms to the requirements of a generic password item in a secure storage container.
/// Generic passwords are the most common types of things that are stored securely.
public protocol GenericPasswordSecureStorable: AccountBasedSecureStorable, DescribableSecureStorable, CommentableSecureStorable, CreatorDesignatableSecureStorable, LabellableSecureStorable, TypeDesignatableSecureStorable, IsInvisibleAssignableSecureStorable, IsNegativeAssignableSecureStorable {
/// The service to which the type belongs
var service: String { get }
// Optional properties
var generic: NSData? { get }
}
// Add extension to allow for optional properties in protocol
public extension GenericPasswordSecureStorable {
var generic: NSData? { return nil}
}
// dear god what have i done...
public protocol GenericPasswordSecureStorableResultType: GenericPasswordSecureStorable, SecureStorableResultType, AccountBasedSecureStorableResultType, DescribableSecureStorableResultType, CommentableSecureStorableResultType, CreatorDesignatableSecureStorableResultType, LabellableSecureStorableResultType, TypeDesignatableSecureStorableResultType, IsInvisibleAssignableSecureStorableResultType, IsNegativeAssignableSecureStorableResultType {}
public extension GenericPasswordSecureStorableResultType {
var service: String {
return resultDictionary[String(kSecAttrService)] as! String
}
var generic: NSData? {
return resultDictionary[String(kSecAttrGeneric)] as? NSData
}
}
public extension SecureStorable where Self : GenericPasswordSecureStorable {
private var genericPasswordBaseStoragePropertyDictionary: [String: AnyObject] {
var dictionary = [String: AnyObject?]()
dictionary[String(kSecAttrService)] = service
dictionary[String(kSecAttrGeneric)] = generic
dictionary[String(kSecClass)] = LocksmithSecurityClass.GenericPassword.rawValue
dictionary = Dictionary(initial: dictionary, toMerge: describableSecureStoragePropertyDictionary)
let toMergeWith = [
secureStorableBaseStoragePropertyDictionary,
accountSecureStoragePropertyDictionary,
describableSecureStoragePropertyDictionary,
commentableSecureStoragePropertyDictionary,
creatorDesignatableSecureStoragePropertyDictionary,
typeDesignatableSecureStoragePropertyDictionary,
labellableSecureStoragePropertyDictionary,
isInvisibleSecureStoragePropertyDictionary,
isNegativeSecureStoragePropertyDictionary
]
for dict in toMergeWith {
dictionary = Dictionary(initial: dictionary, toMerge: dict)
}
return Dictionary(withoutOptionalValues: dictionary)
}
}
// MARK: - InternetPasswordSecureStorable
/// A protocol that indicates a type conforms to the requirements of an internet password in a secure storage container.
public protocol InternetPasswordSecureStorable: AccountBasedSecureStorable, DescribableSecureStorable, CommentableSecureStorable, CreatorDesignatableSecureStorable, TypeDesignatableSecureStorable, IsInvisibleAssignableSecureStorable, IsNegativeAssignableSecureStorable {
var server: String { get }
var port: Int { get }
var internetProtocol: LocksmithInternetProtocol { get }
var authenticationType: LocksmithInternetAuthenticationType { get }
var securityDomain: String? { get }
var path: String? { get }
}
public extension InternetPasswordSecureStorable {
var securityDomain: String? { return nil }
var path: String? { return nil }
}
public protocol InternetPasswordSecureStorableResultType: AccountBasedSecureStorableResultType, DescribableSecureStorableResultType, CommentableSecureStorableResultType, CreatorDesignatableSecureStorableResultType, TypeDesignatableSecureStorableResultType, IsInvisibleAssignableSecureStorableResultType, IsNegativeAssignableSecureStorableResultType {}
public extension InternetPasswordSecureStorableResultType {
private func stringFromResultDictionary(key: CFString) -> String? {
return resultDictionary[String(key)] as? String
}
var server: String {
return stringFromResultDictionary(kSecAttrServer)!
}
var port: Int {
return resultDictionary[String(kSecAttrPort)] as! Int
}
var internetProtocol: LocksmithInternetProtocol {
return LocksmithInternetProtocol(rawValue: stringFromResultDictionary(kSecAttrProtocol)!)!
}
var authenticationType: LocksmithInternetAuthenticationType {
return LocksmithInternetAuthenticationType(rawValue: stringFromResultDictionary(kSecAttrAuthenticationType)!)!
}
var securityDomain: String? {
return stringFromResultDictionary(kSecAttrSecurityDomain)
}
var path: String? {
return stringFromResultDictionary(kSecAttrPath)
}
}
// MARK: - CertificateSecureStorable
public protocol CertificateSecureStorable: SecureStorable {}
// MARK: - KeySecureStorable
public protocol KeySecureStorable: SecureStorable {}
// MARK: - CreateableSecureStorable
/// Conformance to this protocol indicates that your type is able to be created and saved to a secure storage container.
public protocol CreateableSecureStorable: SecureStorable {
var data: [String: AnyObject] { get }
var performCreateRequestClosure: PerformRequestClosureType { get }
func createInSecureStore() throws
func updateInSecureStore() throws
}
// MARK: - ReadableSecureStorable
/// Conformance to this protocol indicates that your type is able to be read from a secure storage container.
public protocol ReadableSecureStorable: SecureStorable {
var performReadRequestClosure: PerformRequestClosureType { get }
func readFromSecureStore() -> SecureStorableResultType?
}
public extension ReadableSecureStorable {
var performReadRequestClosure: PerformRequestClosureType {
return { (requestReference: CFDictionaryRef, inout result: AnyObject?) in
return withUnsafeMutablePointer(&result) { SecItemCopyMatching(requestReference, UnsafeMutablePointer($0)) }
}
}
func readFromSecureStore() -> SecureStorableResultType? {
// This must be implemented here so that we can properly override it in the type-specific implementations
return nil
}
}
public extension ReadableSecureStorable where Self : GenericPasswordSecureStorable {
var asReadableSecureStoragePropertyDictionary: [String: AnyObject] {
var old = genericPasswordBaseStoragePropertyDictionary
old[String(kSecReturnData)] = true
old[String(kSecMatchLimit)] = kSecMatchLimitOne
old[String(kSecReturnAttributes)] = kCFBooleanTrue
return old
}
}
public extension ReadableSecureStorable where Self : InternetPasswordSecureStorable {
var asReadableSecureStoragePropertyDictionary: [String: AnyObject] {
var old = internetPasswordBaseStoragePropertyDictionary
old[String(kSecReturnData)] = true
old[String(kSecMatchLimit)] = kSecMatchLimitOne
old[String(kSecReturnAttributes)] = kCFBooleanTrue
return old
}
}
struct GenericPasswordResult: GenericPasswordSecureStorableResultType {
var resultDictionary: [String: AnyObject]
}
public extension ReadableSecureStorable where Self : GenericPasswordSecureStorable {
func readFromSecureStore() -> GenericPasswordSecureStorableResultType? {
do {
if let result = try performSecureStorageAction(performReadRequestClosure, secureStoragePropertyDictionary: asReadableSecureStoragePropertyDictionary) {
return GenericPasswordResult(resultDictionary: result)
} else {
return nil
}
} catch {
return nil
}
}
}
public extension ReadableSecureStorable where Self : InternetPasswordSecureStorable {
func readFromSecureStore() -> InternetPasswordSecureStorableResultType? {
do {
if let result = try performSecureStorageAction(performReadRequestClosure, secureStoragePropertyDictionary: asReadableSecureStoragePropertyDictionary) {
return InternetPasswordResult(resultDictionary: result)
} else {
return nil
}
} catch {
return nil
}
}
}
// MARK: - DeleteableSecureStorable
/// Conformance to this protocol indicates that your type is able to be deleted from a secure storage container.
public protocol DeleteableSecureStorable: SecureStorable {
var performDeleteRequestClosure: PerformRequestClosureType { get }
func deleteFromSecureStore() throws
}
// MARK: - Default property dictionaries
extension CreateableSecureStorable {
func updateInSecureStore(query: [String: AnyObject]) throws {
var attributesToUpdate = query
attributesToUpdate[String(kSecClass)] = nil
let status = SecItemUpdate(query, attributesToUpdate)
if let error = LocksmithError(fromStatusCode: Int(status)) {
if error == .NotFound || error == .NotAvailable {
try self.createInSecureStore()
} else {
throw error
}
} else {
if status != errSecSuccess {
throw LocksmithError.Undefined
}
}
}
}
public extension CreateableSecureStorable where Self : GenericPasswordSecureStorable {
var asCreateableSecureStoragePropertyDictionary: [String: AnyObject] {
var old = genericPasswordBaseStoragePropertyDictionary
old[String(kSecValueData)] = NSKeyedArchiver.archivedDataWithRootObject(data)
return old
}
}
public extension CreateableSecureStorable where Self : GenericPasswordSecureStorable {
func createInSecureStore() throws {
try performSecureStorageAction(performCreateRequestClosure, secureStoragePropertyDictionary: asCreateableSecureStoragePropertyDictionary)
}
func updateInSecureStore() throws {
try self.updateInSecureStore(self.asCreateableSecureStoragePropertyDictionary)
}
}
public extension CreateableSecureStorable where Self : InternetPasswordSecureStorable {
var asCreateableSecureStoragePropertyDictionary: [String: AnyObject] {
var old = internetPasswordBaseStoragePropertyDictionary
old[String(kSecValueData)] = NSKeyedArchiver.archivedDataWithRootObject(data)
return old
}
}
public extension CreateableSecureStorable {
var performCreateRequestClosure: PerformRequestClosureType {
return { (requestReference: CFDictionaryRef, inout result: AnyObject?) in
return withUnsafeMutablePointer(&result) { SecItemAdd(requestReference, UnsafeMutablePointer($0)) }
}
}
}
public extension CreateableSecureStorable where Self : InternetPasswordSecureStorable {
func createInSecureStore() throws {
try performSecureStorageAction(performCreateRequestClosure, secureStoragePropertyDictionary: asCreateableSecureStoragePropertyDictionary)
}
func updateInSecureStore() throws {
try self.updateInSecureStore(self.asCreateableSecureStoragePropertyDictionary)
}
}
public extension DeleteableSecureStorable {
var performDeleteRequestClosure: PerformRequestClosureType {
return { (requestReference, _) in
return SecItemDelete(requestReference)
}
}
}
public extension DeleteableSecureStorable where Self : GenericPasswordSecureStorable {
var asDeleteableSecureStoragePropertyDictionary: [String: AnyObject] {
return genericPasswordBaseStoragePropertyDictionary
}
}
public extension DeleteableSecureStorable where Self : InternetPasswordSecureStorable {
var asDeleteableSecureStoragePropertyDictionary: [String: AnyObject] {
return internetPasswordBaseStoragePropertyDictionary
}
}
public extension DeleteableSecureStorable where Self : GenericPasswordSecureStorable {
func deleteFromSecureStore() throws {
try performSecureStorageAction(performDeleteRequestClosure, secureStoragePropertyDictionary: asDeleteableSecureStoragePropertyDictionary)
}
}
public extension DeleteableSecureStorable where Self : InternetPasswordSecureStorable {
func deleteFromSecureStore() throws {
try performSecureStorageAction(performDeleteRequestClosure, secureStoragePropertyDictionary: asDeleteableSecureStoragePropertyDictionary)
}
}
// MARK: ResultTypes
public protocol SecureStorableResultType: SecureStorable {
var resultDictionary: [String: AnyObject] { get }
var data: [String: AnyObject]? { get }
}
struct InternetPasswordResult: InternetPasswordSecureStorableResultType {
var resultDictionary: [String: AnyObject]
}
public extension SecureStorableResultType {
var resultDictionary: [String: AnyObject] {
return [String: AnyObject]()
}
var data: [String: AnyObject]? {
guard let aData = resultDictionary[String(kSecValueData)] as? NSData else {
return nil
}
return NSKeyedUnarchiver.unarchiveObjectWithData(aData) as? [String: AnyObject]
}
}