]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
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, ¬ificationToken, 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"; | |
d8f41ccd A |
282 | nd[(NSString *)kCFUserNotificationTextFieldTitlesKey] = @[@"Passcode"]; |
283 | nd[(__bridge NSString *)SBUserNotificationTextAutocapitalizationType] = @[ @(NO_AUTOCAPITALIZATION) ]; | |
284 | nd[(__bridge NSString *)SBUserNotificationTextAutocapitalizationType] = @[ @(NO_AUTOCORRECTION) ]; | |
285 | ||
286 | flags = kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0); | |
287 | ||
288 | dialog_alert = CFUserNotificationCreate(NULL, 0, flags, &err, (__bridge CFMutableDictionaryRef)nd); | |
289 | if (!dialog_alert) | |
290 | return NO; | |
291 | ||
292 | CFUserNotificationReceiveResponse(dialog_alert, 0, &flags); | |
293 | // the 2 lower bits of the response flags will give the button pressed | |
294 | // 0 --> default | |
295 | // 1 --> alternate | |
296 | if (flags & kCFUserNotificationCancelResponse) { // user cancelled | |
297 | if (dialog_alert) | |
298 | CFRelease(dialog_alert); | |
299 | return NO; | |
300 | } | |
301 | ||
302 | // user clicked OK | |
303 | passcode = CFBridgingRelease(CFUserNotificationGetResponseValue(dialog_alert, kCFUserNotificationTextFieldValuesKey, 0)); | |
304 | // test using MKBUnlockDevice | |
305 | // NSLog(@"PIN: %@", passcode); // TODO: REMOVE THIS!!!! | |
306 | ||
307 | if (dialog_alert) | |
308 | CFRelease(dialog_alert); | |
309 | return [self unlockDeviceWithPasscode:passcode]; | |
310 | } | |
311 | ||
312 | @end |