SSKeychainQuery.m 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. //
  2. // SSKeychainQuery.m
  3. // SSKeychain
  4. //
  5. // Created by Caleb Davenport on 3/19/13.
  6. // Copyright (c) 2013-2014 Sam Soffes. All rights reserved.
  7. //
  8. #import "SSKeychainQuery.h"
  9. #import "SSKeychain.h"
  10. @implementation SSKeychainQuery
  11. @synthesize account = _account;
  12. @synthesize service = _service;
  13. @synthesize label = _label;
  14. @synthesize passwordData = _passwordData;
  15. #if __IPHONE_3_0 && TARGET_OS_IPHONE
  16. @synthesize accessGroup = _accessGroup;
  17. #endif
  18. #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE
  19. @synthesize synchronizationMode = _synchronizationMode;
  20. #endif
  21. #pragma mark - Public
  22. - (BOOL)save:(NSError *__autoreleasing *)error {
  23. OSStatus status = SSKeychainErrorBadArguments;
  24. if (!self.service || !self.account || !self.passwordData) {
  25. if (error) {
  26. *error = [[self class] errorWithCode:status];
  27. }
  28. return NO;
  29. }
  30. NSMutableDictionary *query = nil;
  31. NSMutableDictionary * searchQuery = [self query];
  32. status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, nil);
  33. if (status == errSecSuccess) {//item already exists, update it!
  34. query = [[NSMutableDictionary alloc]init];
  35. [query setObject:self.passwordData forKey:(__bridge id)kSecValueData];
  36. status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
  37. }else if(status == errSecItemNotFound){//item not found, create it!
  38. query = [self query];
  39. if (self.label) {
  40. [query setObject:self.label forKey:(__bridge id)kSecAttrLabel];
  41. }
  42. [query setObject:self.passwordData forKey:(__bridge id)kSecValueData];
  43. #if __IPHONE_4_0 && TARGET_OS_IPHONE
  44. CFTypeRef accessibilityType = [SSKeychain accessibilityType];
  45. if (accessibilityType) {
  46. [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible];
  47. }
  48. #endif
  49. status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
  50. }
  51. if (status != errSecSuccess && error != NULL) {
  52. *error = [[self class] errorWithCode:status];
  53. }
  54. return (status == errSecSuccess);}
  55. - (BOOL)deleteItem:(NSError *__autoreleasing *)error {
  56. OSStatus status = SSKeychainErrorBadArguments;
  57. if (!self.service || !self.account) {
  58. if (error) {
  59. *error = [[self class] errorWithCode:status];
  60. }
  61. return NO;
  62. }
  63. NSMutableDictionary *query = [self query];
  64. #if TARGET_OS_IPHONE
  65. status = SecItemDelete((__bridge CFDictionaryRef)query);
  66. #else
  67. CFTypeRef result = NULL;
  68. [query setObject:@YES forKey:(__bridge id)kSecReturnRef];
  69. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  70. if (status == errSecSuccess) {
  71. status = SecKeychainItemDelete((SecKeychainItemRef)result);
  72. CFRelease(result);
  73. }
  74. #endif
  75. if (status != errSecSuccess && error != NULL) {
  76. *error = [[self class] errorWithCode:status];
  77. }
  78. return (status == errSecSuccess);
  79. }
  80. - (NSArray *)fetchAll:(NSError *__autoreleasing *)error {
  81. OSStatus status = SSKeychainErrorBadArguments;
  82. NSMutableDictionary *query = [self query];
  83. [query setObject:@YES forKey:(__bridge id)kSecReturnAttributes];
  84. [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
  85. #if __IPHONE_4_0 && TARGET_OS_IPHONE
  86. CFTypeRef accessibilityType = [SSKeychain accessibilityType];
  87. if (accessibilityType) {
  88. [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible];
  89. }
  90. #endif
  91. CFTypeRef result = NULL;
  92. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  93. if (status != errSecSuccess && error != NULL) {
  94. *error = [[self class] errorWithCode:status];
  95. return nil;
  96. }
  97. return (__bridge_transfer NSArray *)result;
  98. }
  99. - (BOOL)fetch:(NSError *__autoreleasing *)error {
  100. OSStatus status = SSKeychainErrorBadArguments;
  101. if (!self.service || !self.account) {
  102. if (error) {
  103. *error = [[self class] errorWithCode:status];
  104. }
  105. return NO;
  106. }
  107. CFTypeRef result = NULL;
  108. NSMutableDictionary *query = [self query];
  109. [query setObject:@YES forKey:(__bridge id)kSecReturnData];
  110. [query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
  111. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  112. if (status != errSecSuccess) {
  113. if (error) {
  114. *error = [[self class] errorWithCode:status];
  115. }
  116. return NO;
  117. }
  118. self.passwordData = (__bridge_transfer NSData *)result;
  119. return YES;
  120. }
  121. #pragma mark - Accessors
  122. - (void)setPasswordObject:(id<NSCoding>)object {
  123. self.passwordData = [NSKeyedArchiver archivedDataWithRootObject:object];
  124. }
  125. - (id<NSCoding>)passwordObject {
  126. if ([self.passwordData length]) {
  127. return [NSKeyedUnarchiver unarchiveObjectWithData:self.passwordData];
  128. }
  129. return nil;
  130. }
  131. - (void)setPassword:(NSString *)password {
  132. self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
  133. }
  134. - (NSString *)password {
  135. if ([self.passwordData length]) {
  136. return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding];
  137. }
  138. return nil;
  139. }
  140. #pragma mark - Synchronization Status
  141. #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE
  142. + (BOOL)isSynchronizationAvailable {
  143. #if TARGET_OS_IPHONE
  144. // Apple suggested way to check for 7.0 at runtime
  145. // https://developer.apple.com/library/ios/documentation/userexperience/conceptual/transitionguide/SupportingEarlieriOS.html
  146. return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1;
  147. #else
  148. return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4;
  149. #endif
  150. }
  151. #endif
  152. #pragma mark - Private
  153. - (NSMutableDictionary *)query {
  154. NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
  155. [dictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  156. if (self.service) {
  157. [dictionary setObject:self.service forKey:(__bridge id)kSecAttrService];
  158. }
  159. if (self.account) {
  160. [dictionary setObject:self.account forKey:(__bridge id)kSecAttrAccount];
  161. }
  162. #if __IPHONE_3_0 && TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
  163. if (self.accessGroup) {
  164. [dictionary setObject:self.accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
  165. }
  166. #endif
  167. #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE
  168. if ([[self class] isSynchronizationAvailable]) {
  169. id value;
  170. switch (self.synchronizationMode) {
  171. case SSKeychainQuerySynchronizationModeNo: {
  172. value = @NO;
  173. break;
  174. }
  175. case SSKeychainQuerySynchronizationModeYes: {
  176. value = @YES;
  177. break;
  178. }
  179. case SSKeychainQuerySynchronizationModeAny: {
  180. value = (__bridge id)(kSecAttrSynchronizableAny);
  181. break;
  182. }
  183. }
  184. [dictionary setObject:value forKey:(__bridge id)(kSecAttrSynchronizable)];
  185. }
  186. #endif
  187. return dictionary;
  188. }
  189. + (NSError *)errorWithCode:(OSStatus) code {
  190. NSString *message = nil;
  191. switch (code) {
  192. case errSecSuccess: return nil;
  193. case SSKeychainErrorBadArguments: message = NSLocalizedStringFromTable(@"SSKeychainErrorBadArguments", @"SSKeychain", nil); break;
  194. #if TARGET_OS_IPHONE
  195. case errSecUnimplemented: {
  196. message = NSLocalizedStringFromTable(@"errSecUnimplemented", @"SSKeychain", nil);
  197. break;
  198. }
  199. case errSecParam: {
  200. message = NSLocalizedStringFromTable(@"errSecParam", @"SSKeychain", nil);
  201. break;
  202. }
  203. case errSecAllocate: {
  204. message = NSLocalizedStringFromTable(@"errSecAllocate", @"SSKeychain", nil);
  205. break;
  206. }
  207. case errSecNotAvailable: {
  208. message = NSLocalizedStringFromTable(@"errSecNotAvailable", @"SSKeychain", nil);
  209. break;
  210. }
  211. case errSecDuplicateItem: {
  212. message = NSLocalizedStringFromTable(@"errSecDuplicateItem", @"SSKeychain", nil);
  213. break;
  214. }
  215. case errSecItemNotFound: {
  216. message = NSLocalizedStringFromTable(@"errSecItemNotFound", @"SSKeychain", nil);
  217. break;
  218. }
  219. case errSecInteractionNotAllowed: {
  220. message = NSLocalizedStringFromTable(@"errSecInteractionNotAllowed", @"SSKeychain", nil);
  221. break;
  222. }
  223. case errSecDecode: {
  224. message = NSLocalizedStringFromTable(@"errSecDecode", @"SSKeychain", nil);
  225. break;
  226. }
  227. case errSecAuthFailed: {
  228. message = NSLocalizedStringFromTable(@"errSecAuthFailed", @"SSKeychain", nil);
  229. break;
  230. }
  231. default: {
  232. message = NSLocalizedStringFromTable(@"errSecDefault", @"SSKeychain", nil);
  233. }
  234. #else
  235. default:
  236. message = (__bridge_transfer NSString *)SecCopyErrorMessageString(code, NULL);
  237. #endif
  238. }
  239. NSDictionary *userInfo = nil;
  240. if (message) {
  241. userInfo = @{ NSLocalizedDescriptionKey : message };
  242. }
  243. return [NSError errorWithDomain:kSSKeychainErrorDomain code:code userInfo:userInfo];
  244. }
  245. @end