KCATableViewController.m [plain text]
//
// KCATableViewController.m
// Security
//
// Created by John Hurley on 10/22/12.
//
//
/*
Sample:
(lldb) po allItems
(NSMutableDictionary *) $3 = 0x0855f200 <__NSCFArray 0x855f200>(
{
acct = "Keychain Sync Test Account";
agrp = test;
cdat = "2012-10-04 21:59:46 +0000";
gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
mdat = "2012-10-08 21:02:39 +0000";
pdmn = ak;
svce = "Keychain Sync Test Service";
},
{
acct = "";
agrp = test;
cdat = "2012-10-22 21:08:14 +0000";
gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
mdat = "2012-10-22 21:08:14 +0000";
pdmn = ak;
svce = "";
},
{
acct = iacct;
agrp = test;
cdat = "2012-10-22 21:08:29 +0000";
gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
mdat = "2012-10-22 21:08:29 +0000";
pdmn = ak;
svce = iname;
},
{
acct = bar;
agrp = test;
cdat = "2012-10-23 16:57:03 +0000";
gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
mdat = "2012-10-23 16:57:03 +0000";
pdmn = ak;
svce = baz;
},
{
acct = foo9;
agrp = test;
cdat = "2012-10-24 22:11:54 +0000";
gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
mdat = "2012-10-24 22:11:54 +0000";
pdmn = ak;
svce = passfoo9;
}
)
*/
#import "KCATableViewController.h"
#import "KeychainItemCell.h"
#import "MyKeychain.h"
#import "KCAItemDetailViewController.h"
#import <notify.h>
#import <Security/SecItemInternal.h>
#import <CoreFoundation/CFUserNotification.h>
#import <SpringBoardServices/SpringBoardServices.h>
#if TARGET_OS_EMBEDDED
#import <MobileKeyBag/MobileKeyBag.h>
#endif
@interface KCATableViewController ()
@end
@implementation KCATableViewController
- (void)viewDidLoad
{
NSLog(@"KCATableViewController: viewDidLoad");
[super viewDidLoad];
_lockTimer = [NSTimer timerWithTimeInterval:15.0 target:self selector:@selector(stopLockTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer: _lockTimer forMode: NSDefaultRunLoopMode];
notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, dispatch_get_main_queue(),
^ (int token __unused)
{
NSLog(@"Received [self.tableView reloadData];
});
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
// Invoked immediately prior to initiating a segue. Return NO to prevent the segue from firing. The default implementation returns YES.
if (hasUnlockedRecently)
return YES;
if ([identifier isEqualToString:@"ItemDetail"])
return [self askForPassword];
return YES;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"ItemDetail"])
{
KCAItemDetailViewController *detailViewController = [segue destinationViewController];
NSIndexPath *myIndexPath = [self.tableView indexPathForSelectedRow];
CFIndex row = [myIndexPath row];
//TODO - horribly inefficient !
NSArray *items = [self getItems];
detailViewController.itemDetailModel = [items objectAtIndex: row];
}
}
// MARK: - Table view data source
- (NSArray *)getItems
{
// Each array element is a dictionary. If svce is present, compare
NSArray *allItems = (NSArray *)[[MyKeychain sharedInstance] fetchDictionaryAll];
return [allItems sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *service1 = obj1[(__bridge id)(kSecAttrService)];
NSString *service2 = obj2[(__bridge id)(kSecAttrService)];
return [service1 compare:service2];
}];
return allItems;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1; //TODO
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSInteger count = [[self getItems] count];
// NSLog(@"numberOfRowsInSection: self.navigationController.navigationBar.topItem.title = [NSString stringWithFormat:@"All Items (
// NSLog(@"Items: return count;
}
- (BOOL)itemUpdatedRecently:(NSDictionary *)item
{
const NSTimeInterval recent = 15.0; // within the last 15 seconds
NSDate *modDate = [item[(__bridge id)kSecAttrModificationDate] dateByAddingTimeInterval:recent];
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];
// NSLog(@"Mod date: return [modDate compare:now] == NSOrderedDescending; // i.e. modDate+15s > now
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
KeychainItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"kcTableCell" forIndexPath:(NSIndexPath *)indexPath];
if (cell == nil)
{
NSLog(@"cellForRowAtIndexPath : cell was nil");
cell = [[KeychainItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"kcTableCell"];
}
// Configure the cell...
NSUInteger row = [indexPath row];
NSArray *items = [self getItems];
NSDictionary *theItem = [items objectAtIndex: row];
NSString *svce = [theItem objectForKey: (__bridge id)(kSecAttrService)];
NSString *acct = [theItem objectForKey: (__bridge id)(kSecAttrAccount)];
if ([self itemUpdatedRecently:theItem])
[cell startCellFlasher];
/* else
cell.itemStatus.text = @"";
*/
cell.itemName.text = (svce && [svce length]) ? svce : @"<<no service>>";
cell.itemAccount.text = (acct && [acct length])? acct : @"<<no account>>";
[cell.itemAccount setTextColor:[UIColor grayColor]];
return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
OSStatus status;
NSUInteger row = [indexPath row];
NSArray *items = [self getItems];
NSDictionary *item = [items objectAtIndex: row];
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:item];
[query removeObjectForKey:(__bridge id)kSecValueData];
[query removeObjectForKey:(__bridge id)kSecAttrCreationDate];
[query removeObjectForKey:(__bridge id)kSecAttrModificationDate];
[query removeObjectForKey:(__bridge id)kSecAttrGeneric];
[query removeObjectForKey:@"tomb"];
query[(__bridge id)(kSecClass)] = (__bridge id)(kSecClassGenericPassword);
// NSLog(@"Item: // NSLog(@"Query: status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status)
NSLog(@"Error from SecItemDelete: else
[self.tableView reloadData];
}
// MARK: Ask for PIN
- (void)startLockTimer
{
NSLog(@"startLockTimer");
hasUnlockedRecently = true;
[_lockTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:60.0]];
}
- (void)stopLockTimer:(NSTimer *)timer
{
// NSLog(@"stopLockTimer");
// Call when we hit home button
// [_lockTimer invalidate];
hasUnlockedRecently = false;
}
- (BOOL)unlockDeviceWithPasscode:(NSString *)passcode
{
#if TARGET_OS_EMBEDDED
int status = kMobileKeyBagError;
NSData *passcodeData = [passcode dataUsingEncoding:NSUTF8StringEncoding];
if (MKBGetDeviceLockState(NULL) != kMobileKeyBagDisabled)
status = MKBUnlockDevice((__bridge CFDataRef)passcodeData, NULL);
else
status = kMobileKeyBagSuccess;
// #define kMobileKeyBagDeviceLockedError (-2)
if (status != kMobileKeyBagSuccess)
{
NSLog(@"Could not unlock device. Error: return NO;
}
#endif
[self startLockTimer];
return YES;
}
#define NO_AUTOCAPITALIZATION 0
#define NO_AUTOCORRECTION 1
- (BOOL)askForPassword
{
// Return YES if authenticated
CFUserNotificationRef dialog_alert = 0;
SInt32 err;
NSMutableDictionary *nd = [NSMutableDictionary dictionaryWithCapacity:0];
CFOptionFlags flags = kCFUserNotificationCautionAlertLevel | kCFUserNotificationNoDefaultButtonFlag;
NSString *passcode;
// Header and buttons
nd[(NSString *)kCFUserNotificationAlertHeaderKey] = @"Unlock";
nd[(NSString *)kCFUserNotificationAlertMessageKey] = @"To view details";
nd[(NSString *)kCFUserNotificationDefaultButtonTitleKey] = @"OK";
nd[(NSString *)kCFUserNotificationAlternateButtonTitleKey] = @"Cancel";
nd[(__bridge __strong id)(SBUserNotificationGroupsTextFields)] = (__bridge id)(kCFBooleanTrue);
nd[(NSString *)kCFUserNotificationTextFieldTitlesKey] = @[@"Passcode"];
nd[(__bridge NSString *)SBUserNotificationTextAutocapitalizationType] = @[ @(NO_AUTOCAPITALIZATION) ];
nd[(__bridge NSString *)SBUserNotificationTextAutocapitalizationType] = @[ @(NO_AUTOCORRECTION) ];
flags = kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0);
dialog_alert = CFUserNotificationCreate(NULL, 0, flags, &err, (__bridge CFMutableDictionaryRef)nd);
if (!dialog_alert)
return NO;
CFUserNotificationReceiveResponse(dialog_alert, 0, &flags);
// the 2 lower bits of the response flags will give the button pressed
// 0 --> default
// 1 --> alternate
if (flags & kCFUserNotificationCancelResponse) { // user cancelled
if (dialog_alert)
CFRelease(dialog_alert);
return NO;
}
// user clicked OK
passcode = CFBridgingRelease(CFUserNotificationGetResponseValue(dialog_alert, kCFUserNotificationTextFieldValuesKey, 0));
// test using MKBUnlockDevice
// NSLog(@"PIN:
if (dialog_alert)
CFRelease(dialog_alert);
return [self unlockDeviceWithPasscode:passcode];
}
@end