]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSSOSTests.m
Security-58286.60.28.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSSOSTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <CloudKit/CloudKit.h>
27 #import <XCTest/XCTest.h>
28 #import <OCMock/OCMock.h>
29 #import <notify.h>
30
31 #include <Security/SecItemPriv.h>
32
33 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
34 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
35 #import "keychain/ckks/CKKS.h"
36 #import "keychain/ckks/CKKSKeychainView.h"
37 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
38 #import "keychain/ckks/CKKSItemEncrypter.h"
39 #import "keychain/ckks/CKKSKey.h"
40 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
41 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
42 #import "keychain/ckks/CKKSSynchronizeOperation.h"
43 #import "keychain/ckks/CKKSViewManager.h"
44 #import "keychain/ckks/CKKSZoneStateEntry.h"
45 #import "keychain/ckks/CKKSManifest.h"
46
47 #import "keychain/ckks/tests/MockCloudKit.h"
48
49 #pragma clang diagnostic push
50 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
51 #include <Security/SecureObjectSync/SOSCloudCircle.h>
52 #include <Security/SecureObjectSync/SOSAccountPriv.h>
53 #include <Security/SecureObjectSync/SOSAccount.h>
54 #include <Security/SecureObjectSync/SOSInternal.h>
55 #include <Security/SecureObjectSync/SOSFullPeerInfo.h>
56 #pragma clang diagnostic pop
57
58 #include <Security/SecKey.h>
59 #include <Security/SecKeyPriv.h>
60 #pragma clang diagnostic pop
61
62 @interface CloudKitKeychainSyncingSOSIntegrationTests : CloudKitKeychainSyncingMockXCTest
63
64 @property CKRecordZoneID* engramZoneID;
65 @property CKKSKeychainView* engramView;
66 @property FakeCKZone* engramZone;
67 @property (readonly) ZoneKeys* engramZoneKeys;
68
69 @property CKRecordZoneID* manateeZoneID;
70 @property CKKSKeychainView* manateeView;
71 @property FakeCKZone* manateeZone;
72 @property (readonly) ZoneKeys* manateeZoneKeys;
73
74 @property CKRecordZoneID* autoUnlockZoneID;
75 @property CKKSKeychainView* autoUnlockView;
76 @property FakeCKZone* autoUnlockZone;
77 @property (readonly) ZoneKeys* autoUnlockZoneKeys;
78
79 @property CKRecordZoneID* healthZoneID;
80 @property CKKSKeychainView* healthView;
81 @property FakeCKZone* healthZone;
82 @property (readonly) ZoneKeys* healthZoneKeys;
83
84 @property CKRecordZoneID* applepayZoneID;
85 @property CKKSKeychainView* applepayView;
86 @property FakeCKZone* applepayZone;
87 @property (readonly) ZoneKeys* applepayZoneKeys;
88
89 @end
90
91 @implementation CloudKitKeychainSyncingSOSIntegrationTests
92 + (void)setUp {
93 SecCKKSEnable();
94 SecCKKSResetSyncing();
95 [super setUp];
96 }
97
98 - (void)setUp {
99 // No manifests.
100 (void)[CKKSManifest shouldSyncManifests]; // initialize.
101 SecCKKSSetSyncManifests(false);
102 SecCKKSSetEnforceManifests(false);
103
104 [super setUp];
105 SecCKKSTestSetDisableSOS(false);
106
107 // Wait for the ViewManager to be brought up
108 XCTAssertEqual(0, [self.injectedManager.completedSecCKKSInitialize wait:4*NSEC_PER_SEC], "No timeout waiting for SecCKKSInitialize");
109
110 self.engramZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Engram" ownerName:CKCurrentUserDefaultName];
111 self.engramZone = [[FakeCKZone alloc] initZone: self.engramZoneID];
112 self.zones[self.engramZoneID] = self.engramZone;
113 self.engramView = [[CKKSViewManager manager] findView:@"Engram"];
114 XCTAssertNotNil(self.engramView, "CKKSViewManager created the Engram view");
115 [self.ckksZones addObject:self.engramZoneID];
116
117 self.manateeZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Manatee" ownerName:CKCurrentUserDefaultName];
118 self.manateeZone = [[FakeCKZone alloc] initZone: self.manateeZoneID];
119 self.zones[self.manateeZoneID] = self.manateeZone;
120 self.manateeView = [[CKKSViewManager manager] findView:@"Manatee"];
121 XCTAssertNotNil(self.manateeView, "CKKSViewManager created the Manatee view");
122 [self.ckksZones addObject:self.manateeZoneID];
123
124 self.autoUnlockZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"AutoUnlock" ownerName:CKCurrentUserDefaultName];
125 self.autoUnlockZone = [[FakeCKZone alloc] initZone: self.autoUnlockZoneID];
126 self.zones[self.autoUnlockZoneID] = self.autoUnlockZone;
127 self.autoUnlockView = [[CKKSViewManager manager] findView:@"AutoUnlock"];
128 XCTAssertNotNil(self.autoUnlockView, "CKKSViewManager created the AutoUnlock view");
129 [self.ckksZones addObject:self.autoUnlockZoneID];
130
131 self.healthZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Health" ownerName:CKCurrentUserDefaultName];
132 self.healthZone = [[FakeCKZone alloc] initZone: self.healthZoneID];
133 self.zones[self.healthZoneID] = self.healthZone;
134 self.healthView = [[CKKSViewManager manager] findView:@"Health"];
135 XCTAssertNotNil(self.healthView, "CKKSViewManager created the Health view");
136 [self.ckksZones addObject:self.healthZoneID];
137
138 self.applepayZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"ApplePay" ownerName:CKCurrentUserDefaultName];
139 self.applepayZone = [[FakeCKZone alloc] initZone: self.healthZoneID];
140 self.zones[self.applepayZoneID] = self.applepayZone;
141 self.applepayView = [[CKKSViewManager manager] findView:@"ApplePay"];
142 XCTAssertNotNil(self.applepayView, "CKKSViewManager created the ApplePay view");
143 [self.ckksZones addObject:self.applepayZoneID];
144 }
145
146 + (void)tearDown {
147 SecCKKSTestSetDisableSOS(true);
148 [super tearDown];
149 SecCKKSResetSyncing();
150 }
151
152 - (void)tearDown {
153 // If the test didn't already do this, allow each zone to spin up
154 self.accountStatus = CKAccountStatusNoAccount;
155 [self startCKKSSubsystem];
156
157 [self.engramView halt];
158 [self.engramView waitUntilAllOperationsAreFinished];
159 self.engramView = nil;
160
161 [self.manateeView halt];
162 [self.manateeView waitUntilAllOperationsAreFinished];
163 self.manateeView = nil;
164
165 [self.autoUnlockView halt];
166 [self.autoUnlockView waitUntilAllOperationsAreFinished];
167 self.autoUnlockView = nil;
168
169 [self.healthView halt];
170 [self.healthView waitUntilAllOperationsAreFinished];
171 self.healthView = nil;
172
173 [self.applepayView halt];
174 [self.applepayView waitUntilAllOperationsAreFinished];
175 self.applepayView = nil;
176
177 [super tearDown];
178 }
179
180 - (ZoneKeys*)engramZoneKeys {
181 return self.keys[self.engramZoneID];
182 }
183
184 - (ZoneKeys*)manateeZoneKeys {
185 return self.keys[self.manateeZoneID];
186 }
187
188 -(void)saveFakeKeyHierarchiesToLocalDatabase {
189 for(CKRecordZoneID* zoneID in self.ckksZones) {
190 [self createAndSaveFakeKeyHierarchy: zoneID];
191 }
192 }
193
194 -(void)testAddEngramManateeItems {
195 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
196
197 [self startCKKSSubsystem];
198
199 XCTestExpectation* engramChanged = [self expectChangeForView:self.engramZoneID.zoneName];
200 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
201 XCTestExpectation* manateeChanged = [self expectChangeForView:self.manateeZoneID.zoneName];
202
203 // We expect a single record to be uploaded to the engram view.
204 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.engramZoneID];
205 [self addGenericPassword: @"data" account: @"account-delete-me-engram" viewHint:(NSString*) kSecAttrViewHintEngram];
206
207 OCMVerifyAllWithDelay(self.mockDatabase, 20);
208 [self waitForExpectations:@[engramChanged] timeout:1];
209 [self waitForExpectations:@[pcsChanged] timeout:1];
210
211 pcsChanged = [self expectChangeForView:@"PCS"];
212
213 // We expect a single record to be uploaded to the manatee view.
214 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID];
215 [self addGenericPassword: @"data" account: @"account-delete-me-manatee" viewHint:(NSString*) kSecAttrViewHintManatee];
216
217 OCMVerifyAllWithDelay(self.mockDatabase, 8);
218 [self waitForExpectations:@[manateeChanged] timeout:1];
219 [self waitForExpectations:@[pcsChanged] timeout:1];
220 }
221
222 -(void)testAddAutoUnlockItems {
223 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
224
225 [self startCKKSSubsystem];
226
227 XCTestExpectation* autoUnlockChanged = [self expectChangeForView:self.autoUnlockZoneID.zoneName];
228 // AutoUnlock is NOT is PCS view, so it should not send the fake 'PCS' view notification
229 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
230 pcsChanged.inverted = YES;
231
232 // We expect a single record to be uploaded to the AutoUnlock view.
233 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.autoUnlockZoneID];
234 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintAutoUnlock];
235
236 OCMVerifyAllWithDelay(self.mockDatabase, 8);
237 [self waitForExpectations:@[autoUnlockChanged] timeout:1];
238 [self waitForExpectations:@[pcsChanged] timeout:0.2];
239 }
240
241 -(void)testAddHealthItems {
242 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
243
244 [self startCKKSSubsystem];
245
246 XCTestExpectation* healthChanged = [self expectChangeForView:self.healthZoneID.zoneName];
247 // Health is NOT is PCS view, so it should not send the fake 'PCS' view notification
248 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
249 pcsChanged.inverted = YES;
250
251 // We expect a single record to be uploaded to the Health view.
252 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.healthZoneID];
253 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintHealth];
254
255 OCMVerifyAllWithDelay(self.mockDatabase, 8);
256 [self waitForExpectations:@[healthChanged] timeout:1];
257 [self waitForExpectations:@[pcsChanged] timeout:0.2];
258 }
259
260 -(void)testAddApplePayItems {
261 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
262
263 [self startCKKSSubsystem];
264
265 XCTestExpectation* applepayChanged = [self expectChangeForView:self.applepayZoneID.zoneName];
266 // ApplePay is NOT is PCS view, so it should not send the fake 'PCS' view notification
267 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
268 pcsChanged.inverted = YES;
269
270 // We expect a single record to be uploaded to the ApplePay view.
271 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.applepayZoneID];
272 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintApplePay];
273
274 OCMVerifyAllWithDelay(self.mockDatabase, 8);
275 [self waitForExpectations:@[applepayChanged] timeout:1];
276 [self waitForExpectations:@[pcsChanged] timeout:0.2];
277 }
278
279
280 -(void)testAddOtherViewHintItem {
281 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
282
283 [self startCKKSSubsystem];
284
285 // We expect no uploads to CKKS.
286 [self addGenericPassword: @"data" account: @"account-delete-me-no-viewhint"];
287 [self addGenericPassword: @"data" account: @"account-delete-me-password" viewHint:(NSString*) kSOSViewAutofillPasswords];
288
289 sleep(1);
290 OCMVerifyAllWithDelay(self.mockDatabase, 8);
291 }
292
293 - (void)testReceiveItemInView {
294 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
295 [self startCKKSSubsystem];
296
297 for(CKRecordZoneID* zoneID in self.ckksZones) {
298 [self expectCKKSTLKSelfShareUpload:zoneID];
299 }
300
301 [self waitForKeyHierarchyReadinesses];
302
303 [self findGenericPassword:@"account-delete-me" expecting:errSecItemNotFound];
304
305 CKRecord* ckr = [self createFakeRecord:self.engramZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
306 [self.engramZone addToZone: ckr];
307
308 XCTestExpectation* engramChanged = [self expectChangeForView:self.engramZoneID.zoneName];
309 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
310
311 // Trigger a notification (with hilariously fake data)
312 [self.engramView notifyZoneChange:nil];
313
314 [self.engramView waitForFetchAndIncomingQueueProcessing];
315 [self findGenericPassword:@"account-delete-me" expecting:errSecSuccess];
316
317 [self waitForExpectations:@[engramChanged] timeout:1];
318 [self waitForExpectations:@[pcsChanged] timeout:1];
319 }
320
321 - (void)testFindManateePiggyTLKs {
322 [self saveFakeKeyHierarchyToLocalDatabase:self.manateeZoneID];
323 [self saveTLKMaterialToKeychain:self.manateeZoneID];
324
325 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
326
327 [self deleteTLKMaterialFromKeychain:self.manateeZoneID];
328
329 [self SOSPiggyBackAddToKeychain:piggyTLKs];
330
331 NSError* error = nil;
332 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
333 XCTAssertNil(error, "No error loading tlk from piggy contents");
334 }
335
336 - (void)testFindPiggyTLKs {
337 [self putFakeKeyHierachiesInCloudKit];
338 [self putFakeDeviceStatusesInCloudKit];
339 [self saveTLKsToKeychain];
340
341 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
342
343 [self deleteTLKMaterialsFromKeychain];
344
345 [self SOSPiggyBackAddToKeychain:piggyTLKs];
346
347 NSError* error = nil;
348 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
349 XCTAssertNil(error, "No error loading manatee tlk from piggy contents");
350
351 [self.engramZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
352 XCTAssertNil(error, "No error loading engram tlk from piggy contents");
353
354 [self.autoUnlockZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
355 XCTAssertNil(error, "No error loading AutoUnlock tlk from piggy contents");
356
357 [self.healthZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
358 XCTAssertNil(error, "No error loading Health tlk from piggy contents");
359
360 [self.applepayZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
361 XCTAssertNil(error, "No error loading ApplePay tlk from piggy contents");
362 }
363
364 -(NSString*)fileForStorage
365 {
366 static dispatch_once_t onceToken;
367 static NSString *tempPath = NULL;
368 dispatch_once(&onceToken, ^{
369 tempPath = [[[[NSFileManager defaultManager] temporaryDirectory] URLByAppendingPathComponent:@"PiggyPacket"] path];
370
371 });
372 return tempPath;
373 }
374
375 -(void)testPiggybackingData{
376 [self putFakeKeyHierachiesInCloudKit];
377 [self saveTLKsToKeychain];
378
379 for(CKRecordZoneID* zoneID in self.ckksZones) {
380 [self expectCKKSTLKSelfShareUpload:zoneID];
381 }
382 [self startCKKSSubsystem];
383
384 [self waitForKeyHierarchyReadinesses];
385
386 OCMVerifyAllWithDelay(self.mockDatabase, 8);
387
388 /*
389 * Pull data from keychain and view manager
390 */
391
392 NSDictionary* piggydata = [self SOSPiggyBackCopyFromKeychain];
393 NSArray<NSData *>* icloudidentities = piggydata[@"idents"];
394 NSArray<NSDictionary *>* tlks = piggydata[@"tlk"];
395
396 XCTAssertEqual([tlks count], [[self.injectedManager viewList] count], "TLKs not same as views");
397
398 XCTAssertNotNil(tlks, "tlks not set");
399 XCTAssertNotEqual([tlks count], (NSUInteger)0, "0 tlks");
400 XCTAssertNotNil(icloudidentities, "idents not set");
401 XCTAssertNotEqual([icloudidentities count], (NSUInteger)0, "0 icloudidentities");
402
403 NSData *initial = SOSPiggyCreateInitialSyncData(icloudidentities, tlks);
404
405 XCTAssertNotNil(initial, "Initial not set");
406 BOOL writeStatus = [initial writeToFile:[self fileForStorage] options:NSDataWritingAtomic error: nil];
407 XCTAssertTrue(writeStatus, "had trouble writing to disk");
408 XCTAssertNotEqual((int)[initial length], 0, "initial sync data is greater than 0");
409
410 /*
411 * Check that they make it accross
412 */
413
414 const uint8_t* der = [initial bytes];
415 const uint8_t *der_end = der + [initial length];
416
417 NSDictionary *result = SOSPiggyCopyInitialSyncData(&der, der_end);
418 XCTAssertNotNil(result, "Initial not set");
419 NSArray *copiedTLKs = result[@"tlks"];
420 XCTAssertNotNil(copiedTLKs, "tlks not set");
421 XCTAssertEqual([copiedTLKs count], [tlks count], "tlks count not same");
422
423 NSArray *copiediCloudidentities = result[@"idents"];
424 XCTAssertNotNil(copiediCloudidentities, "idents not set");
425 XCTAssertEqual([copiediCloudidentities count], [icloudidentities count], "ident count not same");
426 }
427
428 -(void)testVerifyTLKSorting {
429 char key[32*2] = {0};
430 NSArray<NSDictionary *> *tlks = @[
431 @{
432 @"acct" : @"11111111",
433 @"srvr" : @"Manatee",
434 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
435 @"auth" : @YES,
436 },
437 @{
438 @"acct" : @"55555555",
439 @"srvr" : @"Health",
440 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
441 },
442 @{
443 @"acct" : @"22222222",
444 @"srvr" : @"Engram",
445 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
446 @"auth" : @YES,
447 },
448 @{
449 @"acct" : @"44444444",
450 @"srvr" : @"Manatee",
451 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
452 },
453 @{
454 @"acct" : @"33333333",
455 @"srvr" : @"Health",
456 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
457 @"auth" : @YES,
458 },
459 ];
460
461 NSArray<NSDictionary *>* sortedTLKs = SOSAccountSortTLKS(tlks);
462 XCTAssertNotNil(sortedTLKs, "sortedTLKs not set");
463
464 NSArray<NSString *> *expectedOrder = @[ @"11111111", @"22222222", @"33333333", @"44444444", @"55555555"];
465 [sortedTLKs enumerateObjectsUsingBlock:^(NSDictionary *tlk, NSUInteger idx, BOOL * _Nonnull stop) {
466 NSString *uuid = tlk[@"acct"];
467 XCTAssertEqualObjects(uuid, expectedOrder[idx], "wrong order");
468 }];
469 }
470
471
472 - (void)testAcceptExistingPiggyKeyHierarchy {
473 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
474 // Test also begins with the TLK having arrived in the local keychain (via SOS)
475 [self putFakeKeyHierachiesInCloudKit];
476 [self saveTLKsToKeychain];
477 NSDictionary* piggyTLKS = [self SOSPiggyBackCopyFromKeychain];
478 [self SOSPiggyBackAddToKeychain:piggyTLKS];
479 [self deleteTLKMaterialsFromKeychain];
480
481 // The CKKS subsystem should write a TLK Share for each view
482 for(CKRecordZoneID* zoneID in self.ckksZones) {
483 [self expectCKKSTLKSelfShareUpload:zoneID];
484 }
485
486 // Spin up CKKS subsystem.
487 [self startCKKSSubsystem];
488
489 [self.manateeView waitForKeyHierarchyReadiness];
490
491 OCMVerifyAllWithDelay(self.mockDatabase, 8);
492
493 // Verify that there are three local keys, and three local current key records
494 __weak __typeof(self) weakSelf = self;
495 [self.manateeView dispatchSync: ^bool{
496 __strong __typeof(weakSelf) strongSelf = weakSelf;
497 XCTAssertNotNil(strongSelf, "self exists");
498
499 NSError* error = nil;
500
501 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.manateeZoneID error:&error];
502 XCTAssertNil(error, "no error fetching keys");
503 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
504
505 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:strongSelf.manateeZoneID error:&error];
506 XCTAssertNil(error, "no error fetching current keys");
507 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
508
509 // Ensure that the manatee syncable TLK is created from a piggy
510 NSDictionary* query = @{
511 (id)kSecClass : (id)kSecClassInternetPassword,
512 (id)kSecAttrNoLegacy : @YES,
513 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
514 (id)kSecAttrDescription: SecCKKSKeyClassTLK,
515 (id)kSecAttrAccount: strongSelf.manateeZoneKeys.tlk.uuid,
516 (id)kSecAttrServer: strongSelf.manateeZoneID.zoneName,
517 (id)kSecAttrSynchronizable: @YES,
518 (id)kSecReturnAttributes: @YES,
519 (id)kSecReturnData: @YES,
520 };
521 CFTypeRef result = nil;
522 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), "Found a syncable TLK");
523 XCTAssertNotNil((__bridge id) result, "Received a result from SecItemCopyMatching");
524 CFReleaseNull(result);
525
526 return false;
527 }];
528 }
529
530 - (void)putFakeDeviceStatusesInCloudKit {
531 [self putFakeDeviceStatusInCloudKit: self.engramZoneID];
532 [self putFakeDeviceStatusInCloudKit: self.manateeZoneID];
533 [self putFakeDeviceStatusInCloudKit: self.autoUnlockZoneID];
534 [self putFakeDeviceStatusInCloudKit: self.healthZoneID];
535 [self putFakeDeviceStatusInCloudKit: self.applepayZoneID];
536 }
537
538 -(void)putFakeKeyHierachiesInCloudKit{
539 [self putFakeKeyHierarchyInCloudKit: self.engramZoneID];
540 [self putFakeKeyHierarchyInCloudKit: self.manateeZoneID];
541 [self putFakeKeyHierarchyInCloudKit: self.autoUnlockZoneID];
542 [self putFakeKeyHierarchyInCloudKit: self.healthZoneID];
543 [self putFakeKeyHierarchyInCloudKit: self.applepayZoneID];
544 }
545 -(void)saveTLKsToKeychain{
546 [self saveTLKMaterialToKeychain:self.engramZoneID];
547 [self saveTLKMaterialToKeychain:self.manateeZoneID];
548 [self saveTLKMaterialToKeychain:self.autoUnlockZoneID];
549 [self saveTLKMaterialToKeychain:self.healthZoneID];
550 [self saveTLKMaterialToKeychain:self.applepayZoneID];
551 }
552 -(void)deleteTLKMaterialsFromKeychain{
553 [self deleteTLKMaterialFromKeychain: self.engramZoneID];
554 [self deleteTLKMaterialFromKeychain: self.manateeZoneID];
555 [self deleteTLKMaterialFromKeychain: self.autoUnlockZoneID];
556 [self deleteTLKMaterialFromKeychain: self.healthZoneID];
557 [self deleteTLKMaterialFromKeychain: self.applepayZoneID];
558 }
559
560 -(void)waitForKeyHierarchyReadinesses {
561 [self.manateeView waitForKeyHierarchyReadiness];
562 [self.engramView waitForKeyHierarchyReadiness];
563 [self.autoUnlockView waitForKeyHierarchyReadiness];
564 [self.healthView waitForKeyHierarchyReadiness];
565 [self.applepayView waitForKeyHierarchyReadiness];
566 }
567
568 -(void)testAcceptExistingAndUsePiggyKeyHierarchy {
569 // Test starts with nothing in database, but one in our fake CloudKit.
570 [self putFakeKeyHierachiesInCloudKit];
571 [self putFakeDeviceStatusesInCloudKit];
572 [self saveTLKsToKeychain];
573 NSDictionary* piggyData = [self SOSPiggyBackCopyFromKeychain];
574 [self deleteTLKMaterialsFromKeychain];
575
576 // Spin up CKKS subsystem.
577 [self startCKKSSubsystem];
578
579 // The CKKS subsystem should not try to write anything to the CloudKit database.
580 XCTAssertEqual(0, [self.manateeView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:400*NSEC_PER_SEC], "CKKS entered waitfortlk");
581
582 OCMVerifyAllWithDelay(self.mockDatabase, 8);
583
584 // Now, save the TLKs to the keychain (to simulate them coming in later via piggybacking).
585 for(CKRecordZoneID* zoneID in self.ckksZones) {
586 [self expectCKKSTLKSelfShareUpload:zoneID];
587 }
588
589 [self SOSPiggyBackAddToKeychain:piggyData];
590 [self waitForKeyHierarchyReadinesses];
591
592 // We expect a single record to be uploaded for each key class
593 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassCBlock:self.manateeZoneID message:@"Object was encrypted under class C key in hierarchy"]];
594 [self addGenericPassword: @"data" account: @"account-delete-me-manatee" viewHint:(id)kSecAttrViewHintManatee];
595
596 OCMVerifyAllWithDelay(self.mockDatabase, 8);
597
598 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassABlock:self.manateeZoneID message:@"Object was encrypted under class A key in hierarchy"]];
599
600 [self addGenericPassword:@"asdf"
601 account:@"account-class-A"
602 viewHint:(id)kSecAttrViewHintManatee
603 access:(id)kSecAttrAccessibleWhenUnlocked
604 expecting:errSecSuccess
605 message:@"Adding class A item"];
606 OCMVerifyAllWithDelay(self.mockDatabase, 8);
607 }
608 @end
609
610 #endif // OCTAGON
611