You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
9.5 KiB

  1. //
  2. // FLEXKeychainQuery.m
  3. // FLEXKeychain
  4. //
  5. // Created by Caleb Davenport on 3/19/13.
  6. // Copyright (c) 2013-2014 Sam Soffes. All rights reserved.
  7. //
  8. #import "FLEXKeychainQuery.h"
  9. #import "FLEXKeychain.h"
  10. @implementation FLEXKeychainQuery
  11. #pragma mark - Public
  12. - (BOOL)save:(NSError *__autoreleasing *)error {
  13. OSStatus status = FLEXKeychainErrorBadArguments;
  14. if (!self.service || !self.account || !self.passwordData) {
  15. if (error) {
  16. *error = [self errorWithCode:status];
  17. }
  18. return NO;
  19. }
  20. NSMutableDictionary *query = nil;
  21. NSMutableDictionary * searchQuery = [self query];
  22. status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, nil);
  23. if (status == errSecSuccess) {//item already exists, update it!
  24. query = [[NSMutableDictionary alloc]init];
  25. query[(__bridge id)kSecValueData] = self.passwordData;
  26. #if __IPHONE_4_0 && TARGET_OS_IPHONE
  27. CFTypeRef accessibilityType = [FLEXKeychain accessibilityType];
  28. if (accessibilityType) {
  29. query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessibilityType;
  30. }
  31. #endif
  32. status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
  33. }else if (status == errSecItemNotFound){//item not found, create it!
  34. query = [self query];
  35. if (self.label) {
  36. query[(__bridge id)kSecAttrLabel] = self.label;
  37. }
  38. query[(__bridge id)kSecValueData] = self.passwordData;
  39. #if __IPHONE_4_0 && TARGET_OS_IPHONE
  40. CFTypeRef accessibilityType = [FLEXKeychain accessibilityType];
  41. if (accessibilityType) {
  42. query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessibilityType;
  43. }
  44. #endif
  45. status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
  46. }
  47. if (status != errSecSuccess && error != NULL) {
  48. *error = [self errorWithCode:status];
  49. }
  50. return (status == errSecSuccess);
  51. }
  52. - (BOOL)deleteItem:(NSError *__autoreleasing *)error {
  53. OSStatus status = FLEXKeychainErrorBadArguments;
  54. if (!self.service || !self.account) {
  55. if (error) {
  56. *error = [self errorWithCode:status];
  57. }
  58. return NO;
  59. }
  60. NSMutableDictionary *query = [self query];
  61. #if TARGET_OS_IPHONE
  62. status = SecItemDelete((__bridge CFDictionaryRef)query);
  63. #else
  64. // On Mac OS, SecItemDelete will not delete a key created in a different
  65. // app, nor in a different version of the same app.
  66. //
  67. // To replicate the issue, save a password, change to the code and
  68. // rebuild the app, and then attempt to delete that password.
  69. //
  70. // This was true in OS X 10.6 and probably later versions as well.
  71. //
  72. // Work around it by using SecItemCopyMatching and SecKeychainItemDelete.
  73. CFTypeRef result = NULL;
  74. query[(__bridge id)kSecReturnRef] = @YES;
  75. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  76. if (status == errSecSuccess) {
  77. status = SecKeychainItemDelete((SecKeychainItemRef)result);
  78. CFRelease(result);
  79. }
  80. #endif
  81. if (status != errSecSuccess && error != NULL) {
  82. *error = [self errorWithCode:status];
  83. }
  84. return (status == errSecSuccess);
  85. }
  86. - (NSArray *)fetchAll:(NSError *__autoreleasing *)error {
  87. NSMutableDictionary *query = [self query];
  88. query[(__bridge id)kSecReturnAttributes] = @YES;
  89. query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
  90. #if __IPHONE_4_0 && TARGET_OS_IPHONE
  91. CFTypeRef accessibilityType = [FLEXKeychain accessibilityType];
  92. if (accessibilityType) {
  93. query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessibilityType;
  94. }
  95. #endif
  96. CFTypeRef result = NULL;
  97. OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  98. if (status != errSecSuccess && error != NULL) {
  99. *error = [self errorWithCode:status];
  100. return nil;
  101. }
  102. return (__bridge_transfer NSArray *)result;
  103. }
  104. - (BOOL)fetch:(NSError *__autoreleasing *)error {
  105. OSStatus status = FLEXKeychainErrorBadArguments;
  106. if (!self.service || !self.account) {
  107. if (error) {
  108. *error = [self errorWithCode:status];
  109. }
  110. return NO;
  111. }
  112. CFTypeRef result = NULL;
  113. NSMutableDictionary *query = [self query];
  114. query[(__bridge id)kSecReturnData] = @YES;
  115. query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
  116. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  117. if (status != errSecSuccess) {
  118. if (error) {
  119. *error = [self errorWithCode:status];
  120. }
  121. return NO;
  122. }
  123. self.passwordData = (__bridge_transfer NSData *)result;
  124. return YES;
  125. }
  126. #pragma mark - Accessors
  127. - (void)setPasswordObject:(id<NSCoding>)object {
  128. self.passwordData = [NSKeyedArchiver archivedDataWithRootObject:object];
  129. }
  130. - (id<NSCoding>)passwordObject {
  131. if (self.passwordData.length) {
  132. return [NSKeyedUnarchiver unarchiveObjectWithData:self.passwordData];
  133. }
  134. return nil;
  135. }
  136. - (void)setPassword:(NSString *)password {
  137. self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
  138. }
  139. - (NSString *)password {
  140. if (self.passwordData.length) {
  141. return [NSString stringWithCString:self.passwordData.bytes encoding:NSUTF8StringEncoding];
  142. }
  143. return nil;
  144. }
  145. #pragma mark - Synchronization Status
  146. #ifdef FLEXKEYCHAIN_SYNCHRONIZATION_AVAILABLE
  147. + (BOOL)isSynchronizationAvailable {
  148. #if TARGET_OS_IPHONE
  149. return YES;
  150. #else
  151. return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4;
  152. #endif
  153. }
  154. #endif
  155. #pragma mark - Private
  156. - (NSMutableDictionary *)query {
  157. NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
  158. dictionary[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
  159. if (self.service) {
  160. dictionary[(__bridge id)kSecAttrService] = self.service;
  161. }
  162. if (self.account) {
  163. dictionary[(__bridge id)kSecAttrAccount] = self.account;
  164. }
  165. #ifdef FLEXKEYCHAIN_ACCESS_GROUP_AVAILABLE
  166. #if !TARGET_IPHONE_SIMULATOR
  167. if (self.accessGroup) {
  168. dictionary[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
  169. }
  170. #endif
  171. #endif
  172. #ifdef FLEXKEYCHAIN_SYNCHRONIZATION_AVAILABLE
  173. if ([[self class] isSynchronizationAvailable]) {
  174. id value;
  175. switch (self.synchronizationMode) {
  176. case FLEXKeychainQuerySynchronizationModeNo: {
  177. value = @NO;
  178. break;
  179. }
  180. case FLEXKeychainQuerySynchronizationModeYes: {
  181. value = @YES;
  182. break;
  183. }
  184. case FLEXKeychainQuerySynchronizationModeAny: {
  185. value = (__bridge id)(kSecAttrSynchronizableAny);
  186. break;
  187. }
  188. }
  189. dictionary[(__bridge id)(kSecAttrSynchronizable)] = value;
  190. }
  191. #endif
  192. return dictionary;
  193. }
  194. - (NSError *)errorWithCode:(OSStatus)code {
  195. static dispatch_once_t onceToken;
  196. static NSBundle *resourcesBundle = nil;
  197. dispatch_once(&onceToken, ^{
  198. NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"FLEXKeychain" withExtension:@"bundle"];
  199. resourcesBundle = [NSBundle bundleWithURL:url];
  200. });
  201. NSString *message = nil;
  202. switch (code) {
  203. case errSecSuccess: return nil;
  204. case FLEXKeychainErrorBadArguments: message = NSLocalizedStringFromTableInBundle(@"FLEXKeychainErrorBadArguments", @"FLEXKeychain", resourcesBundle, nil); break;
  205. #if TARGET_OS_IPHONE
  206. case errSecUnimplemented: {
  207. message = NSLocalizedStringFromTableInBundle(@"errSecUnimplemented", @"FLEXKeychain", resourcesBundle, nil);
  208. break;
  209. }
  210. case errSecParam: {
  211. message = NSLocalizedStringFromTableInBundle(@"errSecParam", @"FLEXKeychain", resourcesBundle, nil);
  212. break;
  213. }
  214. case errSecAllocate: {
  215. message = NSLocalizedStringFromTableInBundle(@"errSecAllocate", @"FLEXKeychain", resourcesBundle, nil);
  216. break;
  217. }
  218. case errSecNotAvailable: {
  219. message = NSLocalizedStringFromTableInBundle(@"errSecNotAvailable", @"FLEXKeychain", resourcesBundle, nil);
  220. break;
  221. }
  222. case errSecDuplicateItem: {
  223. message = NSLocalizedStringFromTableInBundle(@"errSecDuplicateItem", @"FLEXKeychain", resourcesBundle, nil);
  224. break;
  225. }
  226. case errSecItemNotFound: {
  227. message = NSLocalizedStringFromTableInBundle(@"errSecItemNotFound", @"FLEXKeychain", resourcesBundle, nil);
  228. break;
  229. }
  230. case errSecInteractionNotAllowed: {
  231. message = NSLocalizedStringFromTableInBundle(@"errSecInteractionNotAllowed", @"FLEXKeychain", resourcesBundle, nil);
  232. break;
  233. }
  234. case errSecDecode: {
  235. message = NSLocalizedStringFromTableInBundle(@"errSecDecode", @"FLEXKeychain", resourcesBundle, nil);
  236. break;
  237. }
  238. case errSecAuthFailed: {
  239. message = NSLocalizedStringFromTableInBundle(@"errSecAuthFailed", @"FLEXKeychain", resourcesBundle, nil);
  240. break;
  241. }
  242. default: {
  243. message = NSLocalizedStringFromTableInBundle(@"errSecDefault", @"FLEXKeychain", resourcesBundle, nil);
  244. }
  245. #else
  246. default:
  247. message = (__bridge_transfer NSString *)SecCopyErrorMessageString(code, NULL);
  248. #endif
  249. }
  250. NSDictionary *userInfo = message ? @{ NSLocalizedDescriptionKey : message } : nil;
  251. return [NSError errorWithDomain:kFLEXKeychainErrorDomain code:code userInfo:userInfo];
  252. }
  253. @end