YYKeychain.m 19 KB

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