123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- //
- // FLEXKeychainQuery.m
- // FLEXKeychain
- //
- // Created by Caleb Davenport on 3/19/13.
- // Copyright (c) 2013-2014 Sam Soffes. All rights reserved.
- //
- #import "FLEXKeychainQuery.h"
- #import "FLEXKeychain.h"
- @implementation FLEXKeychainQuery
- #pragma mark - Public
- - (BOOL)save:(NSError *__autoreleasing *)error {
- OSStatus status = FLEXKeychainErrorBadArguments;
- if (!self.service || !self.account || !self.passwordData) {
- if (error) {
- *error = [self errorWithCode:status];
- }
- return NO;
- }
-
- NSMutableDictionary *query = nil;
- NSMutableDictionary * searchQuery = [self query];
- status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, nil);
- if (status == errSecSuccess) {//item already exists, update it!
- query = [[NSMutableDictionary alloc]init];
- query[(__bridge id)kSecValueData] = self.passwordData;
- #if __IPHONE_4_0 && TARGET_OS_IPHONE
- CFTypeRef accessibilityType = FLEXKeychain.accessibilityType;
- if (accessibilityType) {
- query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessibilityType;
- }
- #endif
- status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
- }else if (status == errSecItemNotFound){//item not found, create it!
- query = [self query];
- if (self.label) {
- query[(__bridge id)kSecAttrLabel] = self.label;
- }
- query[(__bridge id)kSecValueData] = self.passwordData;
- #if __IPHONE_4_0 && TARGET_OS_IPHONE
- CFTypeRef accessibilityType = FLEXKeychain.accessibilityType;
- if (accessibilityType) {
- query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessibilityType;
- }
- #endif
- status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
- }
-
- if (status != errSecSuccess && error != NULL) {
- *error = [self errorWithCode:status];
- }
-
- return (status == errSecSuccess);
- }
- - (BOOL)deleteItem:(NSError *__autoreleasing *)error {
- OSStatus status = FLEXKeychainErrorBadArguments;
- if (!self.service || !self.account) {
- if (error) {
- *error = [self errorWithCode:status];
- }
-
- return NO;
- }
-
- NSMutableDictionary *query = [self query];
- #if TARGET_OS_IPHONE
- status = SecItemDelete((__bridge CFDictionaryRef)query);
- #else
- // On Mac OS, SecItemDelete will not delete a key created in a different
- // app, nor in a different version of the same app.
- //
- // To replicate the issue, save a password, change to the code and
- // rebuild the app, and then attempt to delete that password.
- //
- // This was true in OS X 10.6 and probably later versions as well.
- //
- // Work around it by using SecItemCopyMatching and SecKeychainItemDelete.
- CFTypeRef result = NULL;
- query[(__bridge id)kSecReturnRef] = @YES;
- status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
- if (status == errSecSuccess) {
- status = SecKeychainItemDelete((SecKeychainItemRef)result);
- CFRelease(result);
- }
- #endif
-
- if (status != errSecSuccess && error != NULL) {
- *error = [self errorWithCode:status];
- }
-
- return (status == errSecSuccess);
- }
- - (NSArray *)fetchAll:(NSError *__autoreleasing *)error {
- NSMutableDictionary *query = [self query];
- query[(__bridge id)kSecReturnAttributes] = @YES;
- query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
- #if __IPHONE_4_0 && TARGET_OS_IPHONE
- CFTypeRef accessibilityType = FLEXKeychain.accessibilityType;
- if (accessibilityType) {
- query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessibilityType;
- }
- #endif
-
- CFTypeRef result = NULL;
- OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
- if (status != errSecSuccess && error != NULL) {
- *error = [self errorWithCode:status];
- return nil;
- }
-
- return (__bridge_transfer NSArray *)result ?: @[];
- }
- - (BOOL)fetch:(NSError *__autoreleasing *)error {
- OSStatus status = FLEXKeychainErrorBadArguments;
- if (!self.service || !self.account) {
- if (error) {
- *error = [self errorWithCode:status];
- }
- return NO;
- }
-
- CFTypeRef result = NULL;
- NSMutableDictionary *query = [self query];
- query[(__bridge id)kSecReturnData] = @YES;
- query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
- status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
-
- if (status != errSecSuccess) {
- if (error) {
- *error = [self errorWithCode:status];
- }
- return NO;
- }
-
- self.passwordData = (__bridge_transfer NSData *)result;
- return YES;
- }
- #pragma mark - Accessors
- - (void)setPasswordObject:(id<NSCoding>)object {
- self.passwordData = [NSKeyedArchiver archivedDataWithRootObject:object];
- }
- - (id<NSCoding>)passwordObject {
- if (self.passwordData.length) {
- return [NSKeyedUnarchiver unarchiveObjectWithData:self.passwordData];
- }
-
- return nil;
- }
- - (void)setPassword:(NSString *)password {
- self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
- }
- - (NSString *)password {
- if (self.passwordData.length) {
- return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding];
- }
-
- return nil;
- }
- #pragma mark - Synchronization Status
- #ifdef FLEXKEYCHAIN_SYNCHRONIZATION_AVAILABLE
- + (BOOL)isSynchronizationAvailable {
- #if TARGET_OS_IPHONE
- return YES;
- #else
- return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4;
- #endif
- }
- #endif
- #pragma mark - Private
- - (NSMutableDictionary *)query {
- NSMutableDictionary *dictionary = [NSMutableDictionary new];
- dictionary[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
-
- if (self.service) {
- dictionary[(__bridge id)kSecAttrService] = self.service;
- }
-
- if (self.account) {
- dictionary[(__bridge id)kSecAttrAccount] = self.account;
- }
-
- #ifdef FLEXKEYCHAIN_ACCESS_GROUP_AVAILABLE
- #if !TARGET_IPHONE_SIMULATOR
- if (self.accessGroup) {
- dictionary[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
- }
- #endif
- #endif
-
- #ifdef FLEXKEYCHAIN_SYNCHRONIZATION_AVAILABLE
- if ([[self class] isSynchronizationAvailable]) {
- id value;
-
- switch (self.synchronizationMode) {
- case FLEXKeychainQuerySynchronizationModeNo: {
- value = @NO;
- break;
- }
- case FLEXKeychainQuerySynchronizationModeYes: {
- value = @YES;
- break;
- }
- case FLEXKeychainQuerySynchronizationModeAny: {
- value = (__bridge id)(kSecAttrSynchronizableAny);
- break;
- }
- }
-
- dictionary[(__bridge id)(kSecAttrSynchronizable)] = value;
- }
- #endif
-
- return dictionary;
- }
- - (NSError *)errorWithCode:(OSStatus)code {
- static dispatch_once_t onceToken;
- static NSBundle *resourcesBundle = nil;
- dispatch_once(&onceToken, ^{
- NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"FLEXKeychain" withExtension:@"bundle"];
- resourcesBundle = [NSBundle bundleWithURL:url];
- });
-
- NSString *message = nil;
- switch (code) {
- case errSecSuccess: return nil;
- case FLEXKeychainErrorBadArguments: message = NSLocalizedStringFromTableInBundle(@"FLEXKeychainErrorBadArguments", @"FLEXKeychain", resourcesBundle, nil); break;
-
- #if TARGET_OS_IPHONE
- case errSecUnimplemented: {
- message = NSLocalizedStringFromTableInBundle(@"errSecUnimplemented", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecParam: {
- message = NSLocalizedStringFromTableInBundle(@"errSecParam", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecAllocate: {
- message = NSLocalizedStringFromTableInBundle(@"errSecAllocate", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecNotAvailable: {
- message = NSLocalizedStringFromTableInBundle(@"errSecNotAvailable", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecDuplicateItem: {
- message = NSLocalizedStringFromTableInBundle(@"errSecDuplicateItem", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecItemNotFound: {
- message = NSLocalizedStringFromTableInBundle(@"errSecItemNotFound", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecInteractionNotAllowed: {
- message = NSLocalizedStringFromTableInBundle(@"errSecInteractionNotAllowed", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecDecode: {
- message = NSLocalizedStringFromTableInBundle(@"errSecDecode", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- case errSecAuthFailed: {
- message = NSLocalizedStringFromTableInBundle(@"errSecAuthFailed", @"FLEXKeychain", resourcesBundle, nil);
- break;
- }
- default: {
- message = NSLocalizedStringFromTableInBundle(@"errSecDefault", @"FLEXKeychain", resourcesBundle, nil);
- }
- #else
- default:
- message = (__bridge_transfer NSString *)SecCopyErrorMessageString(code, NULL);
- #endif
- }
-
- NSDictionary *userInfo = message ? @{ NSLocalizedDescriptionKey : message } : nil;
- return [NSError errorWithDomain:kFLEXKeychainErrorDomain code:code userInfo:userInfo];
- }
- @end
|