YYKeychain.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. //
  2. // YYKeychain.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 14/10/15.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYKeychain.h"
  12. #import "UIDevice+YYAdd.h"
  13. #import "YYKitMacro.h"
  14. #import <Security/Security.h>
  15. static YYKeychainErrorCode YYKeychainErrorCodeFromOSStatus(OSStatus status) {
  16. switch (status) {
  17. case errSecUnimplemented: return YYKeychainErrorUnimplemented;
  18. case errSecIO: return YYKeychainErrorIO;
  19. case errSecOpWr: return YYKeychainErrorOpWr;
  20. case errSecParam: return YYKeychainErrorParam;
  21. case errSecAllocate: return YYKeychainErrorAllocate;
  22. case errSecUserCanceled: return YYKeychainErrorUserCancelled;
  23. case errSecBadReq: return YYKeychainErrorBadReq;
  24. case errSecInternalComponent: return YYKeychainErrorInternalComponent;
  25. case errSecNotAvailable: return YYKeychainErrorNotAvailable;
  26. case errSecDuplicateItem: return YYKeychainErrorDuplicateItem;
  27. case errSecItemNotFound: return YYKeychainErrorItemNotFound;
  28. case errSecInteractionNotAllowed: return YYKeychainErrorInteractionNotAllowed;
  29. case errSecDecode: return YYKeychainErrorDecode;
  30. case errSecAuthFailed: return YYKeychainErrorAuthFailed;
  31. default: return 0;
  32. }
  33. }
  34. static NSString *YYKeychainErrorDesc(YYKeychainErrorCode code) {
  35. switch (code) {
  36. case YYKeychainErrorUnimplemented:
  37. return @"Function or operation not implemented.";
  38. case YYKeychainErrorIO:
  39. return @"I/O error (bummers)";
  40. case YYKeychainErrorOpWr:
  41. return @"ile already open with with write permission.";
  42. case YYKeychainErrorParam:
  43. return @"One or more parameters passed to a function where not valid.";
  44. case YYKeychainErrorAllocate:
  45. return @"Failed to allocate memory.";
  46. case YYKeychainErrorUserCancelled:
  47. return @"User canceled the operation.";
  48. case YYKeychainErrorBadReq:
  49. return @"Bad parameter or invalid state for operation.";
  50. case YYKeychainErrorInternalComponent:
  51. return @"Inrernal Component";
  52. case YYKeychainErrorNotAvailable:
  53. return @"No keychain is available. You may need to restart your computer.";
  54. case YYKeychainErrorDuplicateItem:
  55. return @"The specified item already exists in the keychain.";
  56. case YYKeychainErrorItemNotFound:
  57. return @"The specified item could not be found in the keychain.";
  58. case YYKeychainErrorInteractionNotAllowed:
  59. return @"User interaction is not allowed.";
  60. case YYKeychainErrorDecode:
  61. return @"Unable to decode the provided data.";
  62. case YYKeychainErrorAuthFailed:
  63. return @"The user name or passphrase you entered is not";
  64. default:
  65. break;
  66. }
  67. return nil;
  68. }
  69. static NSString *YYKeychainAccessibleStr(YYKeychainAccessible e) {
  70. switch (e) {
  71. case YYKeychainAccessibleWhenUnlocked:
  72. return (__bridge NSString *)(kSecAttrAccessibleWhenUnlocked);
  73. case YYKeychainAccessibleAfterFirstUnlock:
  74. return (__bridge NSString *)(kSecAttrAccessibleAfterFirstUnlock);
  75. case YYKeychainAccessibleAlways:
  76. return (__bridge NSString *)(kSecAttrAccessibleAlways);
  77. case YYKeychainAccessibleWhenPasscodeSetThisDeviceOnly:
  78. return (__bridge NSString *)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly);
  79. case YYKeychainAccessibleWhenUnlockedThisDeviceOnly:
  80. return (__bridge NSString *)(kSecAttrAccessibleWhenUnlockedThisDeviceOnly);
  81. case YYKeychainAccessibleAfterFirstUnlockThisDeviceOnly:
  82. return (__bridge NSString *)(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly);
  83. case YYKeychainAccessibleAlwaysThisDeviceOnly:
  84. return (__bridge NSString *)(kSecAttrAccessibleAlwaysThisDeviceOnly);
  85. default:
  86. return nil;
  87. }
  88. }
  89. static YYKeychainAccessible YYKeychainAccessibleEnum(NSString *s) {
  90. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleWhenUnlocked])
  91. return YYKeychainAccessibleWhenUnlocked;
  92. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock])
  93. return YYKeychainAccessibleAfterFirstUnlock;
  94. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAlways])
  95. return YYKeychainAccessibleAlways;
  96. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly])
  97. return YYKeychainAccessibleWhenPasscodeSetThisDeviceOnly;
  98. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleWhenUnlockedThisDeviceOnly])
  99. return YYKeychainAccessibleWhenUnlockedThisDeviceOnly;
  100. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly])
  101. return YYKeychainAccessibleAfterFirstUnlockThisDeviceOnly;
  102. if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly])
  103. return YYKeychainAccessibleAlwaysThisDeviceOnly;
  104. return YYKeychainAccessibleNone;
  105. }
  106. static id YYKeychainQuerySynchonizationID(YYKeychainQuerySynchronizationMode mode) {
  107. switch (mode) {
  108. case YYKeychainQuerySynchronizationModeAny:
  109. return (__bridge id)(kSecAttrSynchronizableAny);
  110. case YYKeychainQuerySynchronizationModeNo:
  111. return (__bridge id)kCFBooleanFalse;
  112. case YYKeychainQuerySynchronizationModeYes:
  113. return (__bridge id)kCFBooleanTrue;
  114. default:
  115. return (__bridge id)(kSecAttrSynchronizableAny);
  116. }
  117. }
  118. static YYKeychainQuerySynchronizationMode YYKeychainQuerySynchonizationEnum(NSNumber *num) {
  119. if ([num isEqualToNumber:@NO]) return YYKeychainQuerySynchronizationModeNo;
  120. if ([num isEqualToNumber:@YES]) return YYKeychainQuerySynchronizationModeYes;
  121. return YYKeychainQuerySynchronizationModeAny;
  122. }
  123. @interface YYKeychainItem ()
  124. @property (nonatomic, readwrite, strong) NSDate *modificationDate;
  125. @property (nonatomic, readwrite, strong) NSDate *creationDate;
  126. @end
  127. @implementation YYKeychainItem
  128. - (void)setPasswordObject:(id <NSCoding> )object {
  129. self.passwordData = [NSKeyedArchiver archivedDataWithRootObject:object];
  130. }
  131. - (id <NSCoding> )passwordObject {
  132. if ([self.passwordData length]) {
  133. return [NSKeyedUnarchiver unarchiveObjectWithData:self.passwordData];
  134. }
  135. return nil;
  136. }
  137. - (void)setPassword:(NSString *)password {
  138. self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
  139. }
  140. - (NSString *)password {
  141. if ([self.passwordData length]) {
  142. return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding];
  143. }
  144. return nil;
  145. }
  146. - (NSMutableDictionary *)queryDic {
  147. NSMutableDictionary *dic = [NSMutableDictionary new];
  148. dic[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
  149. if (self.account) dic[(__bridge id)kSecAttrAccount] = self.account;
  150. if (self.service) dic[(__bridge id)kSecAttrService] = self.service;
  151. if (![UIDevice currentDevice].isSimulator) {
  152. // Remove the access group if running on the iPhone simulator.
  153. //
  154. // Apps that are built for the simulator aren't signed, so there's no keychain access group
  155. // for the simulator to check. This means that all apps can see all keychain items when run
  156. // on the simulator.
  157. //
  158. // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
  159. // simulator will return -25243 (errSecNoAccessForItem).
  160. //
  161. // The access group attribute will be included in items returned by SecItemCopyMatching,
  162. // which is why we need to remove it before updating the item.
  163. if (self.accessGroup) dic[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
  164. }
  165. if (kiOS7Later) {
  166. dic[(__bridge id)kSecAttrSynchronizable] = YYKeychainQuerySynchonizationID(self.synchronizable);
  167. }
  168. return dic;
  169. }
  170. - (NSMutableDictionary *)dic {
  171. NSMutableDictionary *dic = [NSMutableDictionary new];
  172. dic[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
  173. if (self.account) dic[(__bridge id)kSecAttrAccount] = self.account;
  174. if (self.service) dic[(__bridge id)kSecAttrService] = self.service;
  175. if (self.label) dic[(__bridge id)kSecAttrLabel] = self.label;
  176. if (![UIDevice currentDevice].isSimulator) {
  177. // Remove the access group if running on the iPhone simulator.
  178. //
  179. // Apps that are built for the simulator aren't signed, so there's no keychain access group
  180. // for the simulator to check. This means that all apps can see all keychain items when run
  181. // on the simulator.
  182. //
  183. // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
  184. // simulator will return -25243 (errSecNoAccessForItem).
  185. //
  186. // The access group attribute will be included in items returned by SecItemCopyMatching,
  187. // which is why we need to remove it before updating the item.
  188. if (self.accessGroup) dic[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
  189. }
  190. if (kiOS7Later) {
  191. dic[(__bridge id)kSecAttrSynchronizable] = YYKeychainQuerySynchonizationID(self.synchronizable);
  192. }
  193. if (self.accessible) dic[(__bridge id)kSecAttrAccessible] = YYKeychainAccessibleStr(self.accessible);
  194. if (self.passwordData) dic[(__bridge id)kSecValueData] = self.passwordData;
  195. if (self.type != nil) dic[(__bridge id)kSecAttrType] = self.type;
  196. if (self.creater != nil) dic[(__bridge id)kSecAttrCreator] = self.creater;
  197. if (self.comment) dic[(__bridge id)kSecAttrComment] = self.comment;
  198. if (self.descr) dic[(__bridge id)kSecAttrDescription] = self.descr;
  199. return dic;
  200. }
  201. - (instancetype)initWithDic:(NSDictionary *)dic {
  202. if (dic.count == 0) return nil;
  203. self = self.init;
  204. self.service = dic[(__bridge id)kSecAttrService];
  205. self.account = dic[(__bridge id)kSecAttrAccount];
  206. self.passwordData = dic[(__bridge id)kSecValueData];
  207. self.label = dic[(__bridge id)kSecAttrLabel];
  208. self.type = dic[(__bridge id)kSecAttrType];
  209. self.creater = dic[(__bridge id)kSecAttrCreator];
  210. self.comment = dic[(__bridge id)kSecAttrComment];
  211. self.descr = dic[(__bridge id)kSecAttrDescription];
  212. self.modificationDate = dic[(__bridge id)kSecAttrModificationDate];
  213. self.creationDate = dic[(__bridge id)kSecAttrCreationDate];
  214. self.accessGroup = dic[(__bridge id)kSecAttrAccessGroup];
  215. self.accessible = YYKeychainAccessibleEnum(dic[(__bridge id)kSecAttrAccessible]);
  216. self.synchronizable = YYKeychainQuerySynchonizationEnum(dic[(__bridge id)kSecAttrSynchronizable]);
  217. return self;
  218. }
  219. - (id)copyWithZone:(NSZone *)zone {
  220. YYKeychainItem *item = [YYKeychainItem new];
  221. item.service = self.service;
  222. item.account = self.account;
  223. item.passwordData = self.passwordData;
  224. item.label = self.label;
  225. item.type = self.type;
  226. item.creater = self.creater;
  227. item.comment = self.comment;
  228. item.descr = self.descr;
  229. item.modificationDate = self.modificationDate;
  230. item.creationDate = self.creationDate;
  231. item.accessGroup = self.accessGroup;
  232. item.accessible = self.accessible;
  233. item.synchronizable = self.synchronizable;
  234. return item;
  235. }
  236. - (NSString *)description {
  237. NSMutableString *str = @"".mutableCopy;
  238. [str appendString:@"YYKeychainItem:{\n"];
  239. if (self.service) [str appendFormat:@" service:%@,\n", self.service];
  240. if (self.account) [str appendFormat:@" service:%@,\n", self.account];
  241. if (self.password) [str appendFormat:@" service:%@,\n", self.password];
  242. if (self.label) [str appendFormat:@" service:%@,\n", self.label];
  243. if (self.type != nil) [str appendFormat:@" service:%@,\n", self.type];
  244. if (self.creater != nil) [str appendFormat:@" service:%@,\n", self.creater];
  245. if (self.comment) [str appendFormat:@" service:%@,\n", self.comment];
  246. if (self.descr) [str appendFormat:@" service:%@,\n", self.descr];
  247. if (self.modificationDate) [str appendFormat:@" service:%@,\n", self.modificationDate];
  248. if (self.creationDate) [str appendFormat:@" service:%@,\n", self.creationDate];
  249. if (self.accessGroup) [str appendFormat:@" service:%@,\n", self.accessGroup];
  250. [str appendString:@"}"];
  251. return str;
  252. }
  253. @end
  254. @implementation YYKeychain
  255. + (NSString *)getPasswordForService:(NSString *)serviceName
  256. account:(NSString *)account
  257. error:(NSError **)error {
  258. if (!serviceName || !account) {
  259. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  260. return nil;
  261. }
  262. YYKeychainItem *item = [YYKeychainItem new];
  263. item.service = serviceName;
  264. item.account = account;
  265. YYKeychainItem *result = [self selectOneItem:item error:error];
  266. return result.password;
  267. }
  268. + (nullable NSString *)getPasswordForService:(NSString *)serviceName
  269. account:(NSString *)account {
  270. return [self getPasswordForService:serviceName account:account error:NULL];
  271. }
  272. + (BOOL)deletePasswordForService:(NSString *)serviceName
  273. account:(NSString *)account
  274. error:(NSError **)error {
  275. if (!serviceName || !account) {
  276. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  277. return NO;
  278. }
  279. YYKeychainItem *item = [YYKeychainItem new];
  280. item.service = serviceName;
  281. item.account = account;
  282. return [self deleteItem:item error:error];
  283. }
  284. + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account {
  285. return [self deletePasswordForService:serviceName account:account error:NULL];
  286. }
  287. + (BOOL)setPassword:(NSString *)password
  288. forService:(NSString *)serviceName
  289. account:(NSString *)account
  290. error:(NSError **)error {
  291. if (!password || !serviceName || !account) {
  292. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  293. return NO;
  294. }
  295. YYKeychainItem *item = [YYKeychainItem new];
  296. item.service = serviceName;
  297. item.account = account;
  298. YYKeychainItem *result = [self selectOneItem:item error:NULL];
  299. if (result) {
  300. result.password = password;
  301. return [self updateItem:result error:error];
  302. } else {
  303. item.password = password;
  304. return [self insertItem:item error:error];
  305. }
  306. }
  307. + (BOOL)setPassword:(NSString *)password
  308. forService:(NSString *)serviceName
  309. account:(NSString *)account {
  310. return [self setPassword:password forService:serviceName account:account error:NULL];
  311. }
  312. + (BOOL)insertItem:(YYKeychainItem *)item error:(NSError **)error {
  313. if (!item.service || !item.account || !item.passwordData) {
  314. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  315. return NO;
  316. }
  317. NSMutableDictionary *query = [item dic];
  318. OSStatus status = status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
  319. if (status != errSecSuccess) {
  320. if (error) *error = [YYKeychain errorWithCode:status];
  321. return NO;
  322. }
  323. return YES;
  324. }
  325. + (BOOL)insertItem:(YYKeychainItem *)item {
  326. return [self insertItem:item error:NULL];
  327. }
  328. + (BOOL)updateItem:(YYKeychainItem *)item error:(NSError **)error {
  329. if (!item.service || !item.account || !item.passwordData) {
  330. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  331. return NO;
  332. }
  333. NSMutableDictionary *query = [item queryDic];
  334. NSMutableDictionary *update = [item dic];
  335. [update removeObjectForKey:(__bridge id)kSecClass];
  336. if (!query || !update) return NO;
  337. OSStatus status = status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
  338. if (status != errSecSuccess) {
  339. if (error) *error = [YYKeychain errorWithCode:status];
  340. return NO;
  341. }
  342. return YES;
  343. }
  344. + (BOOL)updateItem:(YYKeychainItem *)item {
  345. return [self updateItem:item error:NULL];
  346. }
  347. + (BOOL)deleteItem:(YYKeychainItem *)item error:(NSError **)error {
  348. if (!item.service || !item.account) {
  349. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  350. return NO;
  351. }
  352. NSMutableDictionary *query = [item dic];
  353. OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
  354. if (status != errSecSuccess) {
  355. if (error) *error = [YYKeychain errorWithCode:status];
  356. return NO;
  357. }
  358. return YES;
  359. }
  360. + (BOOL)deleteItem:(YYKeychainItem *)item {
  361. return [self deleteItem:item error:NULL];
  362. }
  363. + (YYKeychainItem *)selectOneItem:(YYKeychainItem *)item error:(NSError **)error {
  364. if (!item.service || !item.account) {
  365. if (error) *error = [YYKeychain errorWithCode:errSecParam];
  366. return nil;
  367. }
  368. NSMutableDictionary *query = [item dic];
  369. query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
  370. query[(__bridge id)kSecReturnAttributes] = @YES;
  371. query[(__bridge id)kSecReturnData] = @YES;
  372. OSStatus status;
  373. CFTypeRef result = NULL;
  374. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  375. if (status != errSecSuccess) {
  376. if (error) *error = [[self class] errorWithCode:status];
  377. return nil;
  378. }
  379. if (!result) return nil;
  380. NSDictionary *dic = nil;
  381. if (CFGetTypeID(result) == CFDictionaryGetTypeID()) {
  382. dic = (__bridge NSDictionary *)(result);
  383. } else if (CFGetTypeID(result) == CFArrayGetTypeID()){
  384. dic = [(__bridge NSArray *)(result) firstObject];
  385. if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
  386. }
  387. if (!dic.count) return nil;
  388. return [[YYKeychainItem alloc] initWithDic:dic];
  389. }
  390. + (YYKeychainItem *)selectOneItem:(YYKeychainItem *)item {
  391. return [self selectOneItem:item error:NULL];
  392. }
  393. + (NSArray *)selectItems:(YYKeychainItem *)item error:(NSError **)error {
  394. NSMutableDictionary *query = [item dic];
  395. query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
  396. query[(__bridge id)kSecReturnAttributes] = @YES;
  397. query[(__bridge id)kSecReturnData] = @YES;
  398. OSStatus status;
  399. CFTypeRef result = NULL;
  400. status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  401. if (status != errSecSuccess && error != NULL) {
  402. *error = [[self class] errorWithCode:status];
  403. return nil;
  404. }
  405. NSMutableArray *res = [NSMutableArray new];
  406. NSDictionary *dic = nil;
  407. if (CFGetTypeID(result) == CFDictionaryGetTypeID()) {
  408. dic = (__bridge NSDictionary *)(result);
  409. YYKeychainItem *item = [[YYKeychainItem alloc] initWithDic:dic];
  410. if (item) [res addObject:item];
  411. } else if (CFGetTypeID(result) == CFArrayGetTypeID()){
  412. for (NSDictionary *dic in (__bridge NSArray *)(result)) {
  413. YYKeychainItem *item = [[YYKeychainItem alloc] initWithDic:dic];
  414. if (item) [res addObject:item];
  415. }
  416. }
  417. return res;
  418. }
  419. + (NSArray *)selectItems:(YYKeychainItem *)item {
  420. return [self selectItems:item error:NULL];
  421. }
  422. + (NSError *)errorWithCode:(OSStatus)osCode {
  423. YYKeychainErrorCode code = YYKeychainErrorCodeFromOSStatus(osCode);
  424. NSString *desc = YYKeychainErrorDesc(code);
  425. NSDictionary *userInfo = desc ? @{ NSLocalizedDescriptionKey : desc } : nil;
  426. return [NSError errorWithDomain:@"com.ibireme.yykit.keychain" code:code userInfo:userInfo];
  427. }
  428. @end