123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- //
- // YYKeychain.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 14/10/15.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYKeychain.h"
- #import "UIDevice+YYAdd.h"
- #import "YYKitMacro.h"
- #import <Security/Security.h>
- static YYKeychainErrorCode YYKeychainErrorCodeFromOSStatus(OSStatus status) {
- switch (status) {
- case errSecUnimplemented: return YYKeychainErrorUnimplemented;
- case errSecIO: return YYKeychainErrorIO;
- case errSecOpWr: return YYKeychainErrorOpWr;
- case errSecParam: return YYKeychainErrorParam;
- case errSecAllocate: return YYKeychainErrorAllocate;
- case errSecUserCanceled: return YYKeychainErrorUserCancelled;
- case errSecBadReq: return YYKeychainErrorBadReq;
- case errSecInternalComponent: return YYKeychainErrorInternalComponent;
- case errSecNotAvailable: return YYKeychainErrorNotAvailable;
- case errSecDuplicateItem: return YYKeychainErrorDuplicateItem;
- case errSecItemNotFound: return YYKeychainErrorItemNotFound;
- case errSecInteractionNotAllowed: return YYKeychainErrorInteractionNotAllowed;
- case errSecDecode: return YYKeychainErrorDecode;
- case errSecAuthFailed: return YYKeychainErrorAuthFailed;
- default: return 0;
- }
- }
- static NSString *YYKeychainErrorDesc(YYKeychainErrorCode code) {
- switch (code) {
- case YYKeychainErrorUnimplemented:
- return @"Function or operation not implemented.";
- case YYKeychainErrorIO:
- return @"I/O error (bummers)";
- case YYKeychainErrorOpWr:
- return @"ile already open with with write permission.";
- case YYKeychainErrorParam:
- return @"One or more parameters passed to a function where not valid.";
- case YYKeychainErrorAllocate:
- return @"Failed to allocate memory.";
- case YYKeychainErrorUserCancelled:
- return @"User canceled the operation.";
- case YYKeychainErrorBadReq:
- return @"Bad parameter or invalid state for operation.";
- case YYKeychainErrorInternalComponent:
- return @"Inrernal Component";
- case YYKeychainErrorNotAvailable:
- return @"No keychain is available. You may need to restart your computer.";
- case YYKeychainErrorDuplicateItem:
- return @"The specified item already exists in the keychain.";
- case YYKeychainErrorItemNotFound:
- return @"The specified item could not be found in the keychain.";
- case YYKeychainErrorInteractionNotAllowed:
- return @"User interaction is not allowed.";
- case YYKeychainErrorDecode:
- return @"Unable to decode the provided data.";
- case YYKeychainErrorAuthFailed:
- return @"The user name or passphrase you entered is not";
- default:
- break;
- }
- return nil;
- }
- static NSString *YYKeychainAccessibleStr(YYKeychainAccessible e) {
- switch (e) {
- case YYKeychainAccessibleWhenUnlocked:
- return (__bridge NSString *)(kSecAttrAccessibleWhenUnlocked);
- case YYKeychainAccessibleAfterFirstUnlock:
- return (__bridge NSString *)(kSecAttrAccessibleAfterFirstUnlock);
- case YYKeychainAccessibleAlways:
- return (__bridge NSString *)(kSecAttrAccessibleAlways);
- case YYKeychainAccessibleWhenPasscodeSetThisDeviceOnly:
- return (__bridge NSString *)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly);
- case YYKeychainAccessibleWhenUnlockedThisDeviceOnly:
- return (__bridge NSString *)(kSecAttrAccessibleWhenUnlockedThisDeviceOnly);
- case YYKeychainAccessibleAfterFirstUnlockThisDeviceOnly:
- return (__bridge NSString *)(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly);
- case YYKeychainAccessibleAlwaysThisDeviceOnly:
- return (__bridge NSString *)(kSecAttrAccessibleAlwaysThisDeviceOnly);
- default:
- return nil;
- }
- }
- static YYKeychainAccessible YYKeychainAccessibleEnum(NSString *s) {
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleWhenUnlocked])
- return YYKeychainAccessibleWhenUnlocked;
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock])
- return YYKeychainAccessibleAfterFirstUnlock;
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAlways])
- return YYKeychainAccessibleAlways;
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly])
- return YYKeychainAccessibleWhenPasscodeSetThisDeviceOnly;
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleWhenUnlockedThisDeviceOnly])
- return YYKeychainAccessibleWhenUnlockedThisDeviceOnly;
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly])
- return YYKeychainAccessibleAfterFirstUnlockThisDeviceOnly;
- if ([s isEqualToString:(__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly])
- return YYKeychainAccessibleAlwaysThisDeviceOnly;
- return YYKeychainAccessibleNone;
- }
- static id YYKeychainQuerySynchonizationID(YYKeychainQuerySynchronizationMode mode) {
- switch (mode) {
- case YYKeychainQuerySynchronizationModeAny:
- return (__bridge id)(kSecAttrSynchronizableAny);
- case YYKeychainQuerySynchronizationModeNo:
- return (__bridge id)kCFBooleanFalse;
- case YYKeychainQuerySynchronizationModeYes:
- return (__bridge id)kCFBooleanTrue;
- default:
- return (__bridge id)(kSecAttrSynchronizableAny);
- }
- }
- static YYKeychainQuerySynchronizationMode YYKeychainQuerySynchonizationEnum(NSNumber *num) {
- if ([num isEqualToNumber:@NO]) return YYKeychainQuerySynchronizationModeNo;
- if ([num isEqualToNumber:@YES]) return YYKeychainQuerySynchronizationModeYes;
- return YYKeychainQuerySynchronizationModeAny;
- }
- @interface YYKeychainItem ()
- @property (nonatomic, readwrite, strong) NSDate *modificationDate;
- @property (nonatomic, readwrite, strong) NSDate *creationDate;
- @end
- @implementation YYKeychainItem
- - (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;
- }
- - (NSMutableDictionary *)queryDic {
- NSMutableDictionary *dic = [NSMutableDictionary new];
-
- dic[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
-
- if (self.account) dic[(__bridge id)kSecAttrAccount] = self.account;
- if (self.service) dic[(__bridge id)kSecAttrService] = self.service;
-
- if (![UIDevice currentDevice].isSimulator) {
- // Remove the access group if running on the iPhone simulator.
- //
- // Apps that are built for the simulator aren't signed, so there's no keychain access group
- // for the simulator to check. This means that all apps can see all keychain items when run
- // on the simulator.
- //
- // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
- // simulator will return -25243 (errSecNoAccessForItem).
- //
- // The access group attribute will be included in items returned by SecItemCopyMatching,
- // which is why we need to remove it before updating the item.
- if (self.accessGroup) dic[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
- }
-
- if (kiOS7Later) {
- dic[(__bridge id)kSecAttrSynchronizable] = YYKeychainQuerySynchonizationID(self.synchronizable);
- }
-
- return dic;
- }
- - (NSMutableDictionary *)dic {
- NSMutableDictionary *dic = [NSMutableDictionary new];
-
- dic[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
-
- if (self.account) dic[(__bridge id)kSecAttrAccount] = self.account;
- if (self.service) dic[(__bridge id)kSecAttrService] = self.service;
- if (self.label) dic[(__bridge id)kSecAttrLabel] = self.label;
-
- if (![UIDevice currentDevice].isSimulator) {
- // Remove the access group if running on the iPhone simulator.
- //
- // Apps that are built for the simulator aren't signed, so there's no keychain access group
- // for the simulator to check. This means that all apps can see all keychain items when run
- // on the simulator.
- //
- // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
- // simulator will return -25243 (errSecNoAccessForItem).
- //
- // The access group attribute will be included in items returned by SecItemCopyMatching,
- // which is why we need to remove it before updating the item.
- if (self.accessGroup) dic[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
- }
-
- if (kiOS7Later) {
- dic[(__bridge id)kSecAttrSynchronizable] = YYKeychainQuerySynchonizationID(self.synchronizable);
- }
-
- if (self.accessible) dic[(__bridge id)kSecAttrAccessible] = YYKeychainAccessibleStr(self.accessible);
- if (self.passwordData) dic[(__bridge id)kSecValueData] = self.passwordData;
- if (self.type != nil) dic[(__bridge id)kSecAttrType] = self.type;
- if (self.creater != nil) dic[(__bridge id)kSecAttrCreator] = self.creater;
- if (self.comment) dic[(__bridge id)kSecAttrComment] = self.comment;
- if (self.descr) dic[(__bridge id)kSecAttrDescription] = self.descr;
-
- return dic;
- }
- - (instancetype)initWithDic:(NSDictionary *)dic {
- if (dic.count == 0) return nil;
- self = self.init;
-
- self.service = dic[(__bridge id)kSecAttrService];
- self.account = dic[(__bridge id)kSecAttrAccount];
- self.passwordData = dic[(__bridge id)kSecValueData];
- self.label = dic[(__bridge id)kSecAttrLabel];
- self.type = dic[(__bridge id)kSecAttrType];
- self.creater = dic[(__bridge id)kSecAttrCreator];
- self.comment = dic[(__bridge id)kSecAttrComment];
- self.descr = dic[(__bridge id)kSecAttrDescription];
- self.modificationDate = dic[(__bridge id)kSecAttrModificationDate];
- self.creationDate = dic[(__bridge id)kSecAttrCreationDate];
- self.accessGroup = dic[(__bridge id)kSecAttrAccessGroup];
- self.accessible = YYKeychainAccessibleEnum(dic[(__bridge id)kSecAttrAccessible]);
- self.synchronizable = YYKeychainQuerySynchonizationEnum(dic[(__bridge id)kSecAttrSynchronizable]);
-
- return self;
- }
- - (id)copyWithZone:(NSZone *)zone {
- YYKeychainItem *item = [YYKeychainItem new];
- item.service = self.service;
- item.account = self.account;
- item.passwordData = self.passwordData;
- item.label = self.label;
- item.type = self.type;
- item.creater = self.creater;
- item.comment = self.comment;
- item.descr = self.descr;
- item.modificationDate = self.modificationDate;
- item.creationDate = self.creationDate;
- item.accessGroup = self.accessGroup;
- item.accessible = self.accessible;
- item.synchronizable = self.synchronizable;
- return item;
- }
- - (NSString *)description {
- NSMutableString *str = @"".mutableCopy;
- [str appendString:@"YYKeychainItem:{\n"];
- if (self.service) [str appendFormat:@" service:%@,\n", self.service];
- if (self.account) [str appendFormat:@" service:%@,\n", self.account];
- if (self.password) [str appendFormat:@" service:%@,\n", self.password];
- if (self.label) [str appendFormat:@" service:%@,\n", self.label];
- if (self.type != nil) [str appendFormat:@" service:%@,\n", self.type];
- if (self.creater != nil) [str appendFormat:@" service:%@,\n", self.creater];
- if (self.comment) [str appendFormat:@" service:%@,\n", self.comment];
- if (self.descr) [str appendFormat:@" service:%@,\n", self.descr];
- if (self.modificationDate) [str appendFormat:@" service:%@,\n", self.modificationDate];
- if (self.creationDate) [str appendFormat:@" service:%@,\n", self.creationDate];
- if (self.accessGroup) [str appendFormat:@" service:%@,\n", self.accessGroup];
- [str appendString:@"}"];
- return str;
- }
- @end
- @implementation YYKeychain
- + (NSString *)getPasswordForService:(NSString *)serviceName
- account:(NSString *)account
- error:(NSError **)error {
- if (!serviceName || !account) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return nil;
- }
-
- YYKeychainItem *item = [YYKeychainItem new];
- item.service = serviceName;
- item.account = account;
- YYKeychainItem *result = [self selectOneItem:item error:error];
- return result.password;
- }
- + (nullable NSString *)getPasswordForService:(NSString *)serviceName
- account:(NSString *)account {
- return [self getPasswordForService:serviceName account:account error:NULL];
- }
- + (BOOL)deletePasswordForService:(NSString *)serviceName
- account:(NSString *)account
- error:(NSError **)error {
- if (!serviceName || !account) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return NO;
- }
-
- YYKeychainItem *item = [YYKeychainItem new];
- item.service = serviceName;
- item.account = account;
- return [self deleteItem:item error:error];
- }
- + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account {
- return [self deletePasswordForService:serviceName account:account error:NULL];
- }
- + (BOOL)setPassword:(NSString *)password
- forService:(NSString *)serviceName
- account:(NSString *)account
- error:(NSError **)error {
- if (!password || !serviceName || !account) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return NO;
- }
- YYKeychainItem *item = [YYKeychainItem new];
- item.service = serviceName;
- item.account = account;
- YYKeychainItem *result = [self selectOneItem:item error:NULL];
- if (result) {
- result.password = password;
- return [self updateItem:result error:error];
- } else {
- item.password = password;
- return [self insertItem:item error:error];
- }
- }
- + (BOOL)setPassword:(NSString *)password
- forService:(NSString *)serviceName
- account:(NSString *)account {
- return [self setPassword:password forService:serviceName account:account error:NULL];
- }
- + (BOOL)insertItem:(YYKeychainItem *)item error:(NSError **)error {
- if (!item.service || !item.account || !item.passwordData) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return NO;
- }
-
- NSMutableDictionary *query = [item dic];
- OSStatus status = status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
- if (status != errSecSuccess) {
- if (error) *error = [YYKeychain errorWithCode:status];
- return NO;
- }
-
- return YES;
- }
- + (BOOL)insertItem:(YYKeychainItem *)item {
- return [self insertItem:item error:NULL];
- }
- + (BOOL)updateItem:(YYKeychainItem *)item error:(NSError **)error {
- if (!item.service || !item.account || !item.passwordData) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return NO;
- }
-
- NSMutableDictionary *query = [item queryDic];
- NSMutableDictionary *update = [item dic];
- [update removeObjectForKey:(__bridge id)kSecClass];
- if (!query || !update) return NO;
- OSStatus status = status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
- if (status != errSecSuccess) {
- if (error) *error = [YYKeychain errorWithCode:status];
- return NO;
- }
-
- return YES;
- }
- + (BOOL)updateItem:(YYKeychainItem *)item {
- return [self updateItem:item error:NULL];
- }
- + (BOOL)deleteItem:(YYKeychainItem *)item error:(NSError **)error {
- if (!item.service || !item.account) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return NO;
- }
-
- NSMutableDictionary *query = [item dic];
- OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
- if (status != errSecSuccess) {
- if (error) *error = [YYKeychain errorWithCode:status];
- return NO;
- }
-
- return YES;
- }
- + (BOOL)deleteItem:(YYKeychainItem *)item {
- return [self deleteItem:item error:NULL];
- }
- + (YYKeychainItem *)selectOneItem:(YYKeychainItem *)item error:(NSError **)error {
- if (!item.service || !item.account) {
- if (error) *error = [YYKeychain errorWithCode:errSecParam];
- return nil;
- }
-
- NSMutableDictionary *query = [item dic];
- query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
- query[(__bridge id)kSecReturnAttributes] = @YES;
- query[(__bridge id)kSecReturnData] = @YES;
-
- OSStatus status;
- CFTypeRef result = NULL;
- status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
- if (status != errSecSuccess) {
- if (error) *error = [[self class] errorWithCode:status];
- return nil;
- }
- if (!result) return nil;
-
- NSDictionary *dic = nil;
- if (CFGetTypeID(result) == CFDictionaryGetTypeID()) {
- dic = (__bridge NSDictionary *)(result);
- } else if (CFGetTypeID(result) == CFArrayGetTypeID()){
- dic = [(__bridge NSArray *)(result) firstObject];
- if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
- }
- if (!dic.count) return nil;
- return [[YYKeychainItem alloc] initWithDic:dic];
- }
- + (YYKeychainItem *)selectOneItem:(YYKeychainItem *)item {
- return [self selectOneItem:item error:NULL];
- }
- + (NSArray *)selectItems:(YYKeychainItem *)item error:(NSError **)error {
- NSMutableDictionary *query = [item dic];
- query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
- query[(__bridge id)kSecReturnAttributes] = @YES;
- query[(__bridge id)kSecReturnData] = @YES;
-
- OSStatus status;
- CFTypeRef result = NULL;
- status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
- if (status != errSecSuccess && error != NULL) {
- *error = [[self class] errorWithCode:status];
- return nil;
- }
-
- NSMutableArray *res = [NSMutableArray new];
- NSDictionary *dic = nil;
- if (CFGetTypeID(result) == CFDictionaryGetTypeID()) {
- dic = (__bridge NSDictionary *)(result);
- YYKeychainItem *item = [[YYKeychainItem alloc] initWithDic:dic];
- if (item) [res addObject:item];
- } else if (CFGetTypeID(result) == CFArrayGetTypeID()){
- for (NSDictionary *dic in (__bridge NSArray *)(result)) {
- YYKeychainItem *item = [[YYKeychainItem alloc] initWithDic:dic];
- if (item) [res addObject:item];
- }
- }
-
- return res;
- }
- + (NSArray *)selectItems:(YYKeychainItem *)item {
- return [self selectItems:item error:NULL];
- }
- + (NSError *)errorWithCode:(OSStatus)osCode {
- YYKeychainErrorCode code = YYKeychainErrorCodeFromOSStatus(osCode);
- NSString *desc = YYKeychainErrorDesc(code);
- NSDictionary *userInfo = desc ? @{ NSLocalizedDescriptionKey : desc } : nil;
- return [NSError errorWithDomain:@"com.ibireme.yykit.keychain" code:code userInfo:userInfo];
- }
- @end
|