2 // KCATableViewController.m
5 // Created by John Hurley on 10/22/12.
13 (NSMutableDictionary *) $3 = 0x0855f200 <__NSCFArray 0x855f200>(
15 acct = "Keychain Sync Test Account";
17 cdat = "2012-10-04 21:59:46 +0000";
18 gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
19 mdat = "2012-10-08 21:02:39 +0000";
21 svce = "Keychain Sync Test Service";
26 cdat = "2012-10-22 21:08:14 +0000";
27 gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
28 mdat = "2012-10-22 21:08:14 +0000";
35 cdat = "2012-10-22 21:08:29 +0000";
36 gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
37 mdat = "2012-10-22 21:08:29 +0000";
44 cdat = "2012-10-23 16:57:03 +0000";
45 gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
46 mdat = "2012-10-23 16:57:03 +0000";
53 cdat = "2012-10-24 22:11:54 +0000";
54 gena = <4b657963 6861696e 2053796e 63205465 73742050 61737377 6f726420 44617461>;
55 mdat = "2012-10-24 22:11:54 +0000";
62 #import "KCATableViewController.h"
63 #import "KeychainItemCell.h"
64 #import "MyKeychain.h"
65 #import "KCAItemDetailViewController.h"
67 #import <Security/SecItemInternal.h>
68 #import <CoreFoundation/CFUserNotification.h>
69 #import <SpringBoardServices/SpringBoardServices.h>
70 #if TARGET_OS_EMBEDDED
71 #import <MobileKeyBag/MobileKeyBag.h>
75 @interface KCATableViewController ()
78 @implementation KCATableViewController
82 NSLog(@"KCATableViewController: viewDidLoad");
85 _lockTimer = [NSTimer timerWithTimeInterval:15.0 target:self selector:@selector(stopLockTimer:) userInfo:nil repeats:YES];
86 [[NSRunLoop currentRunLoop] addTimer: _lockTimer forMode: NSDefaultRunLoopMode];
88 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, dispatch_get_main_queue(),
89 ^ (int token __unused)
91 NSLog(@"Received %s", kSecServerKeychainChangedNotification);
92 [self.tableView reloadData];
96 - (void)didReceiveMemoryWarning
98 [super didReceiveMemoryWarning];
99 // Dispose of any resources that can be recreated.
102 - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
104 // Invoked immediately prior to initiating a segue. Return NO to prevent the segue from firing. The default implementation returns YES.
105 if (hasUnlockedRecently)
107 if ([identifier isEqualToString:@"ItemDetail"])
108 return [self askForPassword];
112 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
114 if ([[segue identifier] isEqualToString:@"ItemDetail"])
116 KCAItemDetailViewController *detailViewController = [segue destinationViewController];
117 NSIndexPath *myIndexPath = [self.tableView indexPathForSelectedRow];
118 CFIndex row = [myIndexPath row];
119 //TODO - horribly inefficient !
120 NSArray *items = [self getItems];
121 detailViewController.itemDetailModel = [items objectAtIndex: row];
125 // MARK: - Table view data source
127 - (NSArray *)getItems
129 // Each array element is a dictionary. If svce is present, compare
130 NSArray *allItems = (NSArray *)[[MyKeychain sharedInstance] fetchDictionaryAll];
132 return [allItems sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
133 NSString *service1 = obj1[(__bridge id)(kSecAttrService)];
134 NSString *service2 = obj2[(__bridge id)(kSecAttrService)];
135 return [service1 compare:service2];
141 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
143 // Return the number of sections.
147 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
149 // Return the number of rows in the section.
150 NSInteger count = [[self getItems] count];
151 // NSLog(@"numberOfRowsInSection: %d", count);
152 self.navigationController.navigationBar.topItem.title = [NSString stringWithFormat:@"All Items (%ld)", (long)count];
154 // NSLog(@"Items: %@", [self getItems]);
158 - (BOOL)itemUpdatedRecently:(NSDictionary *)item
160 const NSTimeInterval recent = 15.0; // within the last 15 seconds
161 NSDate *modDate = [item[(__bridge id)kSecAttrModificationDate] dateByAddingTimeInterval:recent];
162 NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];
164 // NSLog(@"Mod date: %@, now: %@", modDate, now);
165 return [modDate compare:now] == NSOrderedDescending; // i.e. modDate+15s > now
168 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
170 KeychainItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"kcTableCell" forIndexPath:(NSIndexPath *)indexPath];
173 NSLog(@"cellForRowAtIndexPath : cell was nil");
174 cell = [[KeychainItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"kcTableCell"];
177 // Configure the cell...
179 NSUInteger row = [indexPath row];
181 NSArray *items = [self getItems];
182 NSDictionary *theItem = [items objectAtIndex: row];
184 NSString *svce = [theItem objectForKey: (__bridge id)(kSecAttrService)];
185 NSString *acct = [theItem objectForKey: (__bridge id)(kSecAttrAccount)];
188 if ([self itemUpdatedRecently:theItem])
189 [cell startCellFlasher];
191 cell.itemStatus.text = @"";
193 cell.itemName.text = (svce && [svce length]) ? svce : @"<<no service>>";
194 cell.itemAccount.text = (acct && [acct length])? acct : @"<<no account>>";
196 [cell.itemAccount setTextColor:[UIColor grayColor]];
201 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
204 NSUInteger row = [indexPath row];
206 NSArray *items = [self getItems];
207 NSDictionary *item = [items objectAtIndex: row];
208 NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:item];
210 [query removeObjectForKey:(__bridge id)kSecValueData];
211 [query removeObjectForKey:(__bridge id)kSecAttrCreationDate];
212 [query removeObjectForKey:(__bridge id)kSecAttrModificationDate];
213 [query removeObjectForKey:(__bridge id)kSecAttrGeneric];
214 [query removeObjectForKey:@"tomb"];
215 query[(__bridge id)(kSecClass)] = (__bridge id)(kSecClassGenericPassword);
217 // NSLog(@"Item: %@", item);
218 // NSLog(@"Query: %@", query);
219 status = SecItemDelete((__bridge CFDictionaryRef)query);
221 NSLog(@"Error from SecItemDelete: %d", (int)status);
223 [self.tableView reloadData];
228 - (void)startLockTimer
230 NSLog(@"startLockTimer");
231 hasUnlockedRecently = true;
232 [_lockTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:60.0]];
235 - (void)stopLockTimer:(NSTimer *)timer
237 // NSLog(@"stopLockTimer");
238 // Call when we hit home button
239 // [_lockTimer invalidate];
240 hasUnlockedRecently = false;
243 - (BOOL)unlockDeviceWithPasscode:(NSString *)passcode
245 #if TARGET_OS_EMBEDDED
246 int status = kMobileKeyBagError;
247 NSData *passcodeData = [passcode dataUsingEncoding:NSUTF8StringEncoding];
248 if (MKBGetDeviceLockState(NULL) != kMobileKeyBagDisabled)
249 status = MKBUnlockDevice((__bridge CFDataRef)passcodeData, NULL);
251 status = kMobileKeyBagSuccess;
253 // #define kMobileKeyBagDeviceLockedError (-2)
255 if (status != kMobileKeyBagSuccess)
257 NSLog(@"Could not unlock device. Error: %d", status);
261 [self startLockTimer];
265 #define NO_AUTOCAPITALIZATION 0
266 #define NO_AUTOCORRECTION 1
268 - (BOOL)askForPassword
270 // Return YES if authenticated
271 CFUserNotificationRef dialog_alert = 0;
273 NSMutableDictionary *nd = [NSMutableDictionary dictionaryWithCapacity:0];
274 CFOptionFlags flags = kCFUserNotificationCautionAlertLevel | kCFUserNotificationNoDefaultButtonFlag;
277 // Header and buttons
278 nd[(NSString *)kCFUserNotificationAlertHeaderKey] = @"Unlock";
279 nd[(NSString *)kCFUserNotificationAlertMessageKey] = @"To view details";
280 nd[(NSString *)kCFUserNotificationDefaultButtonTitleKey] = @"OK";
281 nd[(NSString *)kCFUserNotificationAlternateButtonTitleKey] = @"Cancel";
282 nd[(__bridge __strong id)(SBUserNotificationGroupsTextFields)] = (__bridge id)(kCFBooleanTrue);
283 nd[(NSString *)kCFUserNotificationTextFieldTitlesKey] = @[@"Passcode"];
284 nd[(__bridge NSString *)SBUserNotificationTextAutocapitalizationType] = @[ @(NO_AUTOCAPITALIZATION) ];
285 nd[(__bridge NSString *)SBUserNotificationTextAutocapitalizationType] = @[ @(NO_AUTOCORRECTION) ];
287 flags = kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0);
289 dialog_alert = CFUserNotificationCreate(NULL, 0, flags, &err, (__bridge CFMutableDictionaryRef)nd);
293 CFUserNotificationReceiveResponse(dialog_alert, 0, &flags);
294 // the 2 lower bits of the response flags will give the button pressed
297 if (flags & kCFUserNotificationCancelResponse) { // user cancelled
299 CFRelease(dialog_alert);
304 passcode = CFBridgingRelease(CFUserNotificationGetResponseValue(dialog_alert, kCFUserNotificationTextFieldValuesKey, 0));
305 // test using MKBUnlockDevice
306 // NSLog(@"PIN: %@", passcode); // TODO: REMOVE THIS!!!!
309 CFRelease(dialog_alert);
310 return [self unlockDeviceWithPasscode:passcode];