/* |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
|
Abstract: |
|
Keychain with Touch ID demo implementation |
|
*/ |
|
#import "AAPLKeychainTestsViewController.h" |
|
@import Security; |
|
@implementation AAPLKeychainTestsViewController |
|
- (void)viewDidLoad |
{ |
[super viewDidLoad]; |
|
// prepare the actions which can be tested in this class |
self.tests = @[ |
[[AAPLTest alloc] initWithName:NSLocalizedString(@"ADD_ITEM", nil) details:@"Using SecItemAdd()" selector:@selector(addItemAsync)], |
[[AAPLTest alloc] initWithName:NSLocalizedString(@"QUERY_FOR_ITEM", nil) details:@"Using SecItemCopyMatching()" selector:@selector(copyMatchingAsync)], |
[[AAPLTest alloc] initWithName:NSLocalizedString(@"UPDATE_ITEM", nil) details:@"Using SecItemUpdate()" selector:@selector(updateItemAsync)], |
[[AAPLTest alloc] initWithName:NSLocalizedString(@"DELETE_ITEM", nil) details:@"Using SecItemDelete()" selector:@selector(deleteItemAsync)] |
|
]; |
|
} |
|
- (void)viewWillAppear:(BOOL)animated |
{ |
[super viewWillAppear:animated]; |
[self.textView scrollRangeToVisible:NSMakeRange([self.textView.text length], 0)]; |
} |
|
-(void)viewDidLayoutSubviews |
{ |
// just set the proper size for the table view based on its content |
CGFloat height = MIN(self.view.bounds.size.height, self.tableView.contentSize.height); |
self.dynamicViewHeight.constant = height; |
[self.view layoutIfNeeded]; |
} |
|
#pragma mark - Tests |
|
- (void)copyMatchingAsync |
{ |
NSDictionary *query = @{ |
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, |
(__bridge id)kSecAttrService: @"SampleService", |
(__bridge id)kSecReturnData: @YES, |
(__bridge id)kSecUseOperationPrompt: NSLocalizedString(@"AUTHENTICATE_TO_ACCESS_SERVICE_PASSWORD", nil) |
}; |
|
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
CFTypeRef dataTypeRef = NULL; |
NSString *msg; |
|
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef); |
if (status == errSecSuccess) |
{ |
NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef; |
NSString * result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]; |
|
msg = [NSString stringWithFormat:NSLocalizedString(@"RESULT", nil), result]; |
} else { |
msg = [NSString stringWithFormat:NSLocalizedString(@"SEC_ITEM_COPY_MATCHING_STATUS", nil), [self keychainErrorToString:status]]; |
} |
[self printResult:self.textView message:msg]; |
}); |
} |
|
- (void)updateItemAsync |
{ |
NSDictionary *query = @{ |
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, |
(__bridge id)kSecAttrService: @"SampleService", |
(__bridge id)kSecUseOperationPrompt: @"Authenticate to update your password" |
}; |
|
NSDictionary *changes = @{ |
(__bridge id)kSecValueData: [@"UPDATED_SECRET_PASSWORD_TEXT" dataUsingEncoding:NSUTF8StringEncoding] |
}; |
|
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes); |
NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"SEC_ITEM_UPDATE_STATUS", nil), [self keychainErrorToString:status]]; |
[super printResult:self.textView message:msg]; |
}); |
} |
|
- (void)addItemAsync |
{ |
CFErrorRef error = NULL; |
SecAccessControlRef sacObject; |
|
// Should be the secret invalidated when passcode is removed? If not then use kSecAttrAccessibleWhenUnlocked |
sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, |
kSecAccessControlUserPresence, &error); |
if(sacObject == NULL || error != NULL) |
{ |
NSLog(@"can't create sacObject: %@", error); |
self.textView.text = [self.textView.text stringByAppendingString:[NSString stringWithFormat:NSLocalizedString(@"SEC_ITEM_ADD_CAN_CREATE_OBJECT", nil), error]]; |
return; |
} |
|
// we want the operation to fail if there is an item which needs authentication so we will use |
// kSecUseNoAuthenticationUI |
NSDictionary *attributes = @{ |
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, |
(__bridge id)kSecAttrService: @"SampleService", |
(__bridge id)kSecValueData: [@"SECRET_PASSWORD_TEXT" dataUsingEncoding:NSUTF8StringEncoding], |
(__bridge id)kSecUseNoAuthenticationUI: @YES, |
(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject |
}; |
|
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil); |
|
NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"SEC_ITEM_ADD_STATUS", nil), [self keychainErrorToString:status]]; |
[self printResult:self.textView message:msg]; |
}); |
} |
|
- (void)deleteItemAsync |
{ |
NSDictionary *query = @{ |
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, |
(__bridge id)kSecAttrService: @"SampleService" |
}; |
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)(query)); |
|
NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"SEC_ITEM_DELETE_STATUS", nil), [self keychainErrorToString:status]]; |
[super printResult:self.textView message:msg]; |
}); |
} |
|
|
|
#pragma mark - Tools |
|
- (NSString *)keychainErrorToString: (OSStatus)error |
{ |
|
NSString *msg = [NSString stringWithFormat:@"%ld",(long)error]; |
|
switch (error) { |
case errSecSuccess: |
msg = NSLocalizedString(@"SUCCESS", nil); |
break; |
case errSecDuplicateItem: |
msg = NSLocalizedString(@"ERROR_ITEM_ALREADY_EXISTS", nil); |
break; |
case errSecItemNotFound : |
msg = NSLocalizedString(@"ERROR_ITEM_NOT_FOUND", nil); |
break; |
case errSecAuthFailed: |
msg = NSLocalizedString(@"ERROR_ITEM_AUTHENTICATION_FAILED", nil); |
break; |
default: |
break; |
} |
|
return msg; |
} |
|
@end |