]> git.saurik.com Git - apple/security.git/blob - Keychain/KCATableViewController.m
Security-57031.30.12.tar.gz
[apple/security.git] / Keychain / KCATableViewController.m
1 //
2 // KCATableViewController.m
3 // Security
4 //
5 // Created by John Hurley on 10/22/12.
6 //
7 //
8
9 /*
10 Sample:
11
12 (lldb) po allItems
13 (NSMutableDictionary *) $3 = 0x0855f200 <__NSCFArray 0x855f200>(
14 {
15 acct = "Keychain Sync Test Account";
16 agrp = test;
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";
20 pdmn = ak;
21 svce = "Keychain Sync Test Service";
22 },
23 {
24 acct = "";
25 agrp = test;
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";
29 pdmn = ak;
30 svce = "";
31 },
32 {
33 acct = iacct;
34 agrp = test;
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";
38 pdmn = ak;
39 svce = iname;
40 },
41 {
42 acct = bar;
43 agrp = test;
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";
47 pdmn = ak;
48 svce = baz;
49 },
50 {
51 acct = foo9;
52 agrp = test;
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";
56 pdmn = ak;
57 svce = passfoo9;
58 }
59 )
60 */
61
62 #import "KCATableViewController.h"
63 #import "KeychainItemCell.h"
64 #import "MyKeychain.h"
65 #import "KCAItemDetailViewController.h"
66 #import <notify.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>
72 #endif
73
74
75 @interface KCATableViewController ()
76 @end
77
78 @implementation KCATableViewController
79
80 - (void)viewDidLoad
81 {
82 NSLog(@"KCATableViewController: viewDidLoad");
83 [super viewDidLoad];
84
85 _lockTimer = [NSTimer timerWithTimeInterval:15.0 target:self selector:@selector(stopLockTimer:) userInfo:nil repeats:YES];
86 [[NSRunLoop currentRunLoop] addTimer: _lockTimer forMode: NSDefaultRunLoopMode];
87
88 notify_register_dispatch(kSecServerKeychainChangedNotification, &notificationToken, dispatch_get_main_queue(),
89 ^ (int token __unused)
90 {
91 NSLog(@"Received %s", kSecServerKeychainChangedNotification);
92 [self.tableView reloadData];
93 });
94 }
95
96 - (void)didReceiveMemoryWarning
97 {
98 [super didReceiveMemoryWarning];
99 // Dispose of any resources that can be recreated.
100 }
101
102 - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
103 {
104 // Invoked immediately prior to initiating a segue. Return NO to prevent the segue from firing. The default implementation returns YES.
105 if (hasUnlockedRecently)
106 return YES;
107 if ([identifier isEqualToString:@"ItemDetail"])
108 return [self askForPassword];
109 return YES;
110 }
111
112 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
113 {
114 if ([[segue identifier] isEqualToString:@"ItemDetail"])
115 {
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];
122 }
123 }
124
125 // MARK: - Table view data source
126
127 - (NSArray *)getItems
128 {
129 // Each array element is a dictionary. If svce is present, compare
130 NSArray *allItems = (NSArray *)[[MyKeychain sharedInstance] fetchDictionaryAll];
131
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];
136 }];
137
138 return allItems;
139 }
140
141 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
142 {
143 // Return the number of sections.
144 return 1; //TODO
145 }
146
147 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
148 {
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];
153
154 // NSLog(@"Items: %@", [self getItems]);
155 return count;
156 }
157
158 - (BOOL)itemUpdatedRecently:(NSDictionary *)item
159 {
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];
163
164 // NSLog(@"Mod date: %@, now: %@", modDate, now);
165 return [modDate compare:now] == NSOrderedDescending; // i.e. modDate+15s > now
166 }
167
168 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
169 {
170 KeychainItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"kcTableCell" forIndexPath:(NSIndexPath *)indexPath];
171 if (cell == nil)
172 {
173 NSLog(@"cellForRowAtIndexPath : cell was nil");
174 cell = [[KeychainItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"kcTableCell"];
175 }
176
177 // Configure the cell...
178
179 NSUInteger row = [indexPath row];
180
181 NSArray *items = [self getItems];
182 NSDictionary *theItem = [items objectAtIndex: row];
183
184 NSString *svce = [theItem objectForKey: (__bridge id)(kSecAttrService)];
185 NSString *acct = [theItem objectForKey: (__bridge id)(kSecAttrAccount)];
186
187
188 if ([self itemUpdatedRecently:theItem])
189 [cell startCellFlasher];
190 /* else
191 cell.itemStatus.text = @"";
192 */
193 cell.itemName.text = (svce && [svce length]) ? svce : @"<<no service>>";
194 cell.itemAccount.text = (acct && [acct length])? acct : @"<<no account>>";
195
196 [cell.itemAccount setTextColor:[UIColor grayColor]];
197
198 return cell;
199 }
200
201 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
202 {
203 OSStatus status;
204 NSUInteger row = [indexPath row];
205
206 NSArray *items = [self getItems];
207 NSDictionary *item = [items objectAtIndex: row];
208 NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:item];
209
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);
216
217 // NSLog(@"Item: %@", item);
218 // NSLog(@"Query: %@", query);
219 status = SecItemDelete((__bridge CFDictionaryRef)query);
220 if (status)
221 NSLog(@"Error from SecItemDelete: %d", (int)status);
222 else
223 [self.tableView reloadData];
224 }
225
226 // MARK: Ask for PIN
227
228 - (void)startLockTimer
229 {
230 NSLog(@"startLockTimer");
231 hasUnlockedRecently = true;
232 [_lockTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:60.0]];
233 }
234
235 - (void)stopLockTimer:(NSTimer *)timer
236 {
237 // NSLog(@"stopLockTimer");
238 // Call when we hit home button
239 // [_lockTimer invalidate];
240 hasUnlockedRecently = false;
241 }
242
243 - (BOOL)unlockDeviceWithPasscode:(NSString *)passcode
244 {
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);
250 else
251 status = kMobileKeyBagSuccess;
252
253 // #define kMobileKeyBagDeviceLockedError (-2)
254
255 if (status != kMobileKeyBagSuccess)
256 {
257 NSLog(@"Could not unlock device. Error: %d", status);
258 return NO;
259 }
260 #endif
261 [self startLockTimer];
262 return YES;
263 }
264
265 #define NO_AUTOCAPITALIZATION 0
266 #define NO_AUTOCORRECTION 1
267
268 - (BOOL)askForPassword
269 {
270 // Return YES if authenticated
271 CFUserNotificationRef dialog_alert = 0;
272 SInt32 err;
273 NSMutableDictionary *nd = [NSMutableDictionary dictionaryWithCapacity:0];
274 CFOptionFlags flags = kCFUserNotificationCautionAlertLevel | kCFUserNotificationNoDefaultButtonFlag;
275 NSString *passcode;
276
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) ];
286
287 flags = kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0);
288
289 dialog_alert = CFUserNotificationCreate(NULL, 0, flags, &err, (__bridge CFMutableDictionaryRef)nd);
290 if (!dialog_alert)
291 return NO;
292
293 CFUserNotificationReceiveResponse(dialog_alert, 0, &flags);
294 // the 2 lower bits of the response flags will give the button pressed
295 // 0 --> default
296 // 1 --> alternate
297 if (flags & kCFUserNotificationCancelResponse) { // user cancelled
298 if (dialog_alert)
299 CFRelease(dialog_alert);
300 return NO;
301 }
302
303 // user clicked OK
304 passcode = CFBridgingRelease(CFUserNotificationGetResponseValue(dialog_alert, kCFUserNotificationTextFieldValuesKey, 0));
305 // test using MKBUnlockDevice
306 // NSLog(@"PIN: %@", passcode); // TODO: REMOVE THIS!!!!
307
308 if (dialog_alert)
309 CFRelease(dialog_alert);
310 return [self unlockDeviceWithPasscode:passcode];
311 }
312
313 @end