123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- //
- // BKTouchIDManager.m
- // BKPasscodeViewDemo
- //
- // Created by Byungkook Jang on 2014. 10. 12..
- // Copyright (c) 2014년 Byungkook Jang. All rights reserved.
- //
- #import "BKTouchIDManager.h"
- #import <LocalAuthentication/LocalAuthentication.h>
- static NSString *const BKTouchIDManagerPasscodeAccountName = @"passcode";
- static NSString *const BKTouchIDManagerTouchIDEnabledAccountName = @"enabled";
- @interface BKTouchIDManager () {
- dispatch_queue_t _queue;
- }
- @property (nonatomic, strong) NSString *keychainServiceName;
- @end
- @implementation BKTouchIDManager
- - (instancetype)initWithKeychainServiceName:(NSString *)serviceName
- {
- self = [super init];
- if (self) {
-
- _queue = dispatch_queue_create("BKTouchIDManagerQueue", DISPATCH_QUEUE_SERIAL);
-
- NSParameterAssert(serviceName);
-
- self.keychainServiceName = serviceName;
- }
- return self;
- }
- + (BOOL)canUseTouchID
- {
- if (![LAContext class]) {
- return NO;
- }
-
- LAContext *context = [[LAContext alloc] init];
-
- NSError *error = nil;
- BOOL result = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
-
- return result;
- }
- - (void)savePasscode:(NSString *)passcode completionBlock:(void(^)(BOOL success))completionBlock
- {
- NSParameterAssert(passcode);
-
- if (NO == [[self class] canUseTouchID]) {
- if (completionBlock) {
- completionBlock(NO);
- }
- return;
- }
-
- NSString *serviceName = self.keychainServiceName;
- NSData *passcodeData = [passcode dataUsingEncoding:NSUTF8StringEncoding];
-
- dispatch_async(_queue, ^{
-
- BOOL success = [[self class] saveKeychainItemWithServiceName:serviceName
- accountName:BKTouchIDManagerPasscodeAccountName
- data:passcodeData
- sacFlags:kSecAccessControlUserPresence];
-
- if (success) {
-
- BOOL enabled = YES;
-
- success = [[self class] saveKeychainItemWithServiceName:serviceName
- accountName:BKTouchIDManagerTouchIDEnabledAccountName
- data:[NSData dataWithBytes:&enabled length:sizeof(BOOL)]
- sacFlags:0];
- }
-
- if (completionBlock) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completionBlock(success);
- });
- }
- });
- }
- - (void)loadPasscodeWithCompletionBlock:(void (^)(NSString *))completionBlock
- {
- if (NO == [[self class] canUseTouchID]) {
- if (completionBlock) {
- completionBlock(nil);
- }
- return;
- }
-
- NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
- (__bridge id)kSecAttrService: self.keychainServiceName,
- (__bridge id)kSecAttrAccount: BKTouchIDManagerPasscodeAccountName,
- (__bridge id)kSecReturnData: @YES }];
-
- if (self.promptText) {
- query[(__bridge id)kSecUseOperationPrompt] = self.promptText;
- }
-
- dispatch_async(_queue, ^{
-
- CFTypeRef dataTypeRef = NULL;
-
- OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
-
- NSString *result = nil;
-
- if (status == errSecSuccess) {
-
- NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
- result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
- }
-
- if (completionBlock) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completionBlock(result);
- });
- }
- });
- }
- - (void)deletePasscodeWithCompletionBlock:(void (^)(BOOL))completionBlock
- {
- dispatch_async(_queue, ^{
-
- BOOL success = ([[self class] deleteKeychainItemWithServiceName:self.keychainServiceName accountName:BKTouchIDManagerPasscodeAccountName] &&
- [[self class] deleteKeychainItemWithServiceName:self.keychainServiceName accountName:BKTouchIDManagerTouchIDEnabledAccountName]);
-
- if (completionBlock) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completionBlock(success);
- });
- }
- });
- }
- - (BOOL)isTouchIDEnabled
- {
- NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
- (__bridge id)kSecAttrService: self.keychainServiceName,
- (__bridge id)kSecAttrAccount: BKTouchIDManagerTouchIDEnabledAccountName,
- (__bridge id)kSecReturnData: @YES };
-
- CFTypeRef dataTypeRef = NULL;
-
- OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
-
- if (status == errSecSuccess) {
-
- NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
- BOOL result;
- [resultData getBytes:&result length:sizeof(BOOL)];
-
- return result;
-
- } else {
- return NO;
- }
- }
- #pragma mark - Static Methods
- + (BOOL)saveKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data sacFlags:(SecAccessControlCreateFlags)sacFlags
- {
- // try to update first
- BOOL success = [self updateKeychainItemWithServiceName:serviceName accountName:accountName data:data];
-
- if (success) {
- return YES;
- }
-
- // try deleting when update failed (workaround for iOS 8 bug)
- [self deleteKeychainItemWithServiceName:serviceName accountName:accountName];
-
- // try add
- return [self addKeychainItemWithServiceName:serviceName accountName:accountName data:data sacFlags:sacFlags];
- }
- + (BOOL)addKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data sacFlags:(SecAccessControlCreateFlags)sacFlags
- {
- CFErrorRef error = NULL;
- SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
- kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
- sacFlags, &error);
-
- if (sacObject == NULL || error != NULL) {
- return NO;
- }
-
- NSDictionary *attributes = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
- (__bridge id)kSecAttrService: serviceName,
- (__bridge id)kSecAttrAccount: accountName,
- (__bridge id)kSecValueData: data,
- (__bridge id)kSecUseNoAuthenticationUI: @YES,
- (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject };
-
- OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
-
- return (status == errSecSuccess);
- }
- + (BOOL)updateKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data
- {
- NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
- (__bridge id)kSecAttrService: serviceName,
- (__bridge id)kSecAttrAccount: accountName };
-
- NSDictionary *changes = @{ (__bridge id)kSecValueData: data };
-
- OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);
-
- return (status == errSecSuccess);
- }
- + (BOOL)deleteKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName
- {
- NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
- (__bridge id)kSecAttrService: serviceName,
- (__bridge id)kSecAttrAccount: accountName };
-
- OSStatus status = SecItemDelete((__bridge CFDictionaryRef)(query));
- return (status == errSecSuccess || status == errSecItemNotFound);
- }
- @end
|