]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSSOSTests.m
Security-58286.251.4.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 @property CKRecordZoneID* homeZoneID;
90 @property CKKSKeychainView* homeView;
91 @property FakeCKZone* homeZone;
92 @property (readonly) ZoneKeys* homeZoneKeys;
93
94 @property CKRecordZoneID* limitedZoneID;
95 @property CKKSKeychainView* limitedView;
96 @property FakeCKZone* limitedZone;
97 @property (readonly) ZoneKeys* limitedZoneKeys;
98
99 @end
100
101 @implementation CloudKitKeychainSyncingSOSIntegrationTests
102 + (void)setUp {
103 SecCKKSEnable();
104 SecCKKSResetSyncing();
105 [super setUp];
106 }
107
108 - (void)setUp {
109 // No manifests.
110 (void)[CKKSManifest shouldSyncManifests]; // initialize.
111 SecCKKSSetSyncManifests(false);
112 SecCKKSSetEnforceManifests(false);
113
114 [super setUp];
115 SecCKKSTestSetDisableSOS(false);
116
117 // Wait for the ViewManager to be brought up
118 XCTAssertEqual(0, [self.injectedManager.completedSecCKKSInitialize wait:20*NSEC_PER_SEC], "No timeout waiting for SecCKKSInitialize");
119
120 self.engramZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Engram" ownerName:CKCurrentUserDefaultName];
121 self.engramZone = [[FakeCKZone alloc] initZone: self.engramZoneID];
122 self.zones[self.engramZoneID] = self.engramZone;
123 self.engramView = [[CKKSViewManager manager] findView:@"Engram"];
124 [self.ckksViews addObject:self.engramView];
125 XCTAssertNotNil(self.engramView, "CKKSViewManager created the Engram view");
126 [self.ckksZones addObject:self.engramZoneID];
127
128 self.manateeZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Manatee" ownerName:CKCurrentUserDefaultName];
129 self.manateeZone = [[FakeCKZone alloc] initZone: self.manateeZoneID];
130 self.zones[self.manateeZoneID] = self.manateeZone;
131 self.manateeView = [[CKKSViewManager manager] findView:@"Manatee"];
132 [self.ckksViews addObject:self.manateeView];
133 XCTAssertNotNil(self.manateeView, "CKKSViewManager created the Manatee view");
134 [self.ckksZones addObject:self.manateeZoneID];
135
136 self.autoUnlockZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"AutoUnlock" ownerName:CKCurrentUserDefaultName];
137 self.autoUnlockZone = [[FakeCKZone alloc] initZone: self.autoUnlockZoneID];
138 self.zones[self.autoUnlockZoneID] = self.autoUnlockZone;
139 self.autoUnlockView = [[CKKSViewManager manager] findView:@"AutoUnlock"];
140 [self.ckksViews addObject:self.autoUnlockView];
141 XCTAssertNotNil(self.autoUnlockView, "CKKSViewManager created the AutoUnlock view");
142 [self.ckksZones addObject:self.autoUnlockZoneID];
143
144 self.healthZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Health" ownerName:CKCurrentUserDefaultName];
145 self.healthZone = [[FakeCKZone alloc] initZone: self.healthZoneID];
146 self.zones[self.healthZoneID] = self.healthZone;
147 self.healthView = [[CKKSViewManager manager] findView:@"Health"];
148 [self.ckksViews addObject:self.healthView];
149 XCTAssertNotNil(self.healthView, "CKKSViewManager created the Health view");
150 [self.ckksZones addObject:self.healthZoneID];
151
152 self.applepayZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"ApplePay" ownerName:CKCurrentUserDefaultName];
153 self.applepayZone = [[FakeCKZone alloc] initZone: self.healthZoneID];
154 self.zones[self.applepayZoneID] = self.applepayZone;
155 self.applepayView = [[CKKSViewManager manager] findView:@"ApplePay"];
156 [self.ckksViews addObject:self.applepayView];
157 XCTAssertNotNil(self.applepayView, "CKKSViewManager created the ApplePay view");
158 [self.ckksZones addObject:self.applepayZoneID];
159
160 self.homeZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Home" ownerName:CKCurrentUserDefaultName];
161 self.homeZone = [[FakeCKZone alloc] initZone: self.homeZoneID];
162 self.zones[self.homeZoneID] = self.homeZone;
163 self.homeView = [[CKKSViewManager manager] findView:@"Home"];
164 XCTAssertNotNil(self.homeView, "CKKSViewManager created the Home view");
165 [self.ckksZones addObject:self.homeZoneID];
166
167 self.limitedZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"LimitedPeersAllowed" ownerName:CKCurrentUserDefaultName];
168 self.limitedZone = [[FakeCKZone alloc] initZone: self.limitedZoneID];
169 self.zones[self.limitedZoneID] = self.limitedZone;
170 self.limitedView = [[CKKSViewManager manager] findView:@"LimitedPeersAllowed"];
171 XCTAssertNotNil(self.limitedView, "CKKSViewManager created the LimitedPeersAllowed view");
172 [self.ckksZones addObject:self.limitedZoneID];
173 }
174
175 + (void)tearDown {
176 SecCKKSTestSetDisableSOS(true);
177 [super tearDown];
178 SecCKKSResetSyncing();
179 }
180
181 - (void)tearDown {
182 // If the test didn't already do this, allow each zone to spin up
183 self.accountStatus = CKAccountStatusNoAccount;
184 [self startCKKSSubsystem];
185
186 [self.engramView halt];
187 [self.engramView waitUntilAllOperationsAreFinished];
188 self.engramView = nil;
189
190 [self.manateeView halt];
191 [self.manateeView waitUntilAllOperationsAreFinished];
192 self.manateeView = nil;
193
194 [self.autoUnlockView halt];
195 [self.autoUnlockView waitUntilAllOperationsAreFinished];
196 self.autoUnlockView = nil;
197
198 [self.healthView halt];
199 [self.healthView waitUntilAllOperationsAreFinished];
200 self.healthView = nil;
201
202 [self.applepayView halt];
203 [self.applepayView waitUntilAllOperationsAreFinished];
204 self.applepayView = nil;
205
206 [self.homeView halt];
207 [self.homeView waitUntilAllOperationsAreFinished];
208 self.homeView = nil;
209
210 [super tearDown];
211 }
212
213 - (ZoneKeys*)engramZoneKeys {
214 return self.keys[self.engramZoneID];
215 }
216
217 - (ZoneKeys*)manateeZoneKeys {
218 return self.keys[self.manateeZoneID];
219 }
220
221 -(void)saveFakeKeyHierarchiesToLocalDatabase {
222 for(CKRecordZoneID* zoneID in self.ckksZones) {
223 [self createAndSaveFakeKeyHierarchy: zoneID];
224 }
225 }
226
227 -(void)testAllViewsMakeNewKeyHierarchies {
228 // Test starts with nothing anywhere
229
230 // Due to our new cross-zone fetch system, CKKS should only issue one fetch for all zones
231 // Since the tests can sometimes be slow, slow down the fetcher to normal speed
232 [self.injectedManager.zoneChangeFetcher.fetchScheduler changeDelays:2*NSEC_PER_SEC continuingDelay:30*NSEC_PER_SEC];
233 self.silentFetchesAllowed = false;
234 [self expectCKFetch];
235
236 [self startCKKSSubsystem];
237
238 // All zones should upload a key hierarchy
239 for(CKRecordZoneID* zoneID in self.ckksZones) {
240 [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:zoneID];
241 }
242 OCMVerifyAllWithDelay(self.mockDatabase, 20);
243
244 for(CKKSKeychainView* view in self.ckksViews) {
245 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should enter 'ready' for view %@", view);
246 }
247 }
248
249 -(void)testAllViewsAcceptExistingKeyHierarchies {
250 for(CKRecordZoneID* zoneID in self.ckksZones) {
251 [self putFakeKeyHierarchyInCloudKit:zoneID];
252 [self saveTLKMaterialToKeychain:zoneID];
253 [self expectCKKSTLKSelfShareUpload:zoneID];
254 }
255
256 [self.injectedManager.zoneChangeFetcher.fetchScheduler changeDelays:2*NSEC_PER_SEC continuingDelay:30*NSEC_PER_SEC];
257 self.silentFetchesAllowed = false;
258 [self expectCKFetch];
259
260 [self startCKKSSubsystem];
261 OCMVerifyAllWithDelay(self.mockDatabase, 20);
262
263 for(CKKSKeychainView* view in self.ckksViews) {
264 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should enter 'ready' for view %@", view);
265 }
266 }
267
268 -(void)testAddEngramManateeItems {
269 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
270
271 [self startCKKSSubsystem];
272
273 XCTestExpectation* engramChanged = [self expectChangeForView:self.engramZoneID.zoneName];
274 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
275 XCTestExpectation* manateeChanged = [self expectChangeForView:self.manateeZoneID.zoneName];
276
277 // We expect a single record to be uploaded to the engram view.
278 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.engramZoneID];
279 [self addGenericPassword: @"data" account: @"account-delete-me-engram" viewHint:(NSString*) kSecAttrViewHintEngram];
280
281 OCMVerifyAllWithDelay(self.mockDatabase, 20);
282 [self waitForExpectations:@[engramChanged] timeout:1];
283 [self waitForExpectations:@[pcsChanged] timeout:1];
284
285 pcsChanged = [self expectChangeForView:@"PCS"];
286
287 // We expect a single record to be uploaded to the manatee view.
288 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID];
289 [self addGenericPassword: @"data" account: @"account-delete-me-manatee" viewHint:(NSString*) kSecAttrViewHintManatee];
290
291 OCMVerifyAllWithDelay(self.mockDatabase, 20);
292 [self waitForExpectations:@[manateeChanged] timeout:1];
293 [self waitForExpectations:@[pcsChanged] timeout:1];
294 }
295
296 -(void)testAddAutoUnlockItems {
297 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
298
299 [self startCKKSSubsystem];
300
301 XCTestExpectation* autoUnlockChanged = [self expectChangeForView:self.autoUnlockZoneID.zoneName];
302 // AutoUnlock is NOT is PCS view, so it should not send the fake 'PCS' view notification
303 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
304 pcsChanged.inverted = YES;
305
306 // We expect a single record to be uploaded to the AutoUnlock view.
307 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.autoUnlockZoneID];
308 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintAutoUnlock];
309
310 OCMVerifyAllWithDelay(self.mockDatabase, 20);
311 [self waitForExpectations:@[autoUnlockChanged] timeout:1];
312 [self waitForExpectations:@[pcsChanged] timeout:0.2];
313 }
314
315 -(void)testAddHealthItems {
316 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
317
318 [self startCKKSSubsystem];
319
320 XCTestExpectation* healthChanged = [self expectChangeForView:self.healthZoneID.zoneName];
321 // Health is NOT is PCS view, so it should not send the fake 'PCS' view notification
322 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
323 pcsChanged.inverted = YES;
324
325 // We expect a single record to be uploaded to the Health view.
326 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.healthZoneID];
327 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintHealth];
328
329 OCMVerifyAllWithDelay(self.mockDatabase, 20);
330 [self waitForExpectations:@[healthChanged] timeout:1];
331 [self waitForExpectations:@[pcsChanged] timeout:0.2];
332 }
333
334 -(void)testAddApplePayItems {
335 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
336
337 [self startCKKSSubsystem];
338
339 XCTestExpectation* applepayChanged = [self expectChangeForView:self.applepayZoneID.zoneName];
340 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
341
342
343 // We expect a single record to be uploaded to the ApplePay view.
344 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.applepayZoneID];
345 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintApplePay];
346
347 OCMVerifyAllWithDelay(self.mockDatabase, 20);
348 [self waitForExpectations:@[applepayChanged] timeout:1];
349 [self waitForExpectations:@[pcsChanged] timeout:0.2];
350 }
351
352 -(void)testAddHomeItems {
353 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
354
355 [self startCKKSSubsystem];
356
357 XCTestExpectation* homeChanged = [self expectChangeForView:self.homeZoneID.zoneName];
358 // Home is NOT a PCS view, so it should not send the fake 'PCS' view notification
359 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
360 pcsChanged.inverted = YES;
361
362 // We expect a single record to be uploaded to the ApplePay view.
363 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.homeZoneID];
364 [self addGenericPassword: @"data" account: @"account-delete-me-autounlock" viewHint:(NSString*) kSecAttrViewHintHome];
365
366 OCMVerifyAllWithDelay(self.mockDatabase, 20);
367 [self waitForExpectations:@[homeChanged] timeout:1];
368 [self waitForExpectations:@[pcsChanged] timeout:0.2];
369 }
370
371 -(void)testAddLimitedPeersAllowedItems {
372 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
373
374 [self startCKKSSubsystem];
375
376 XCTestExpectation* limitedChanged = [self expectChangeForView:self.limitedZoneID.zoneName];
377 // LimitedPeersAllowed is a PCS view, so it should send the fake 'PCS' view notification
378 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
379
380 // We expect a single record to be uploaded to the LimitedPeersOkay view.
381 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.limitedZoneID];
382 [self addGenericPassword: @"data" account: @"account-delete-me-limited-peers" viewHint:(NSString*) kSecAttrViewHintLimitedPeersAllowed];
383
384 OCMVerifyAllWithDelay(self.mockDatabase, 20);
385 [self waitForExpectations:@[limitedChanged] timeout:1];
386 [self waitForExpectations:@[pcsChanged] timeout:0.2];
387
388 [self waitForKeyHierarchyReadinesses];
389
390 // Let's also test that an item added by a future peer (lacking the right viewhint) doesn't sync
391 NSMutableDictionary* item = [[self fakeRecordDictionary:@"asdf" zoneID:self.limitedZoneID] mutableCopy];
392 item[(id)kSecAttrSyncViewHint] = @"new-view";
393
394 CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"37E6CA21-A586-44E1-9A1C-3EE464C78EB5" zoneID:self.limitedZoneID];
395 CKRecord* ckr = [self newRecord:ckrid withNewItemData:item];
396 [self.limitedZone addToZone:ckr];
397 ckr = [self createFakeRecord:self.limitedZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2AAA" withAccount:@"asdf-exist"];
398 [self.limitedZone addToZone:ckr];
399
400 [self.limitedView notifyZoneChange:nil];
401 [self.limitedView waitForFetchAndIncomingQueueProcessing];
402
403 [self findGenericPassword:@"asdf-exist" expecting:errSecSuccess];
404 [self findGenericPassword:@"asdf" expecting:errSecItemNotFound];
405
406 NSError *error = NULL;
407 XCTAssertEqual([CKKSIncomingQueueEntry countByState:SecCKKSStateZoneMismatch zone:self.limitedZoneID error:&error],
408 1,
409 "Expected a ZoneMismatch entry in incoming queue: %@", error);
410 XCTAssertNil(error, "Should be no error fetching incoming queue entries");
411 }
412
413 -(void)testAddOtherViewHintItem {
414 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
415
416 [self startCKKSSubsystem];
417
418 // We expect no uploads to CKKS.
419 [self addGenericPassword: @"data" account: @"account-delete-me-no-viewhint"];
420 [self addGenericPassword: @"data" account: @"account-delete-me-password" viewHint:(NSString*) kSOSViewAutofillPasswords];
421
422 sleep(1);
423 OCMVerifyAllWithDelay(self.mockDatabase, 20);
424 }
425
426 - (void)testReceiveItemInView {
427 [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
428 [self startCKKSSubsystem];
429
430 for(CKRecordZoneID* zoneID in self.ckksZones) {
431 [self expectCKKSTLKSelfShareUpload:zoneID];
432 }
433
434 [self waitForKeyHierarchyReadinesses];
435
436 [self findGenericPassword:@"account-delete-me" expecting:errSecItemNotFound];
437
438 CKRecord* ckr = [self createFakeRecord:self.engramZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
439 [self.engramZone addToZone: ckr];
440
441 XCTestExpectation* engramChanged = [self expectChangeForView:self.engramZoneID.zoneName];
442 XCTestExpectation* pcsChanged = [self expectChangeForView:@"PCS"];
443
444 // Trigger a notification (with hilariously fake data)
445 [self.engramView notifyZoneChange:nil];
446
447 [self.engramView waitForFetchAndIncomingQueueProcessing];
448 [self findGenericPassword:@"account-delete-me" expecting:errSecSuccess];
449
450 [self waitForExpectations:@[engramChanged] timeout:1];
451 [self waitForExpectations:@[pcsChanged] timeout:1];
452 }
453
454 - (void)testFindManateePiggyTLKs {
455 [self saveFakeKeyHierarchyToLocalDatabase:self.manateeZoneID];
456 [self saveTLKMaterialToKeychain:self.manateeZoneID];
457
458 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
459
460 [self deleteTLKMaterialFromKeychain:self.manateeZoneID];
461
462 [self SOSPiggyBackAddToKeychain:piggyTLKs];
463
464 NSError* error = nil;
465 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
466 XCTAssertNil(error, "No error loading tlk from piggy contents");
467 }
468
469 - (void)testFindPiggyTLKs {
470 [self putFakeKeyHierachiesInCloudKit];
471 [self putFakeDeviceStatusesInCloudKit];
472 [self saveTLKsToKeychain];
473
474 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
475
476 [self deleteTLKMaterialsFromKeychain];
477
478 [self SOSPiggyBackAddToKeychain:piggyTLKs];
479
480 NSError* error = nil;
481 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
482 XCTAssertNil(error, "No error loading manatee tlk from piggy contents");
483
484 [self.engramZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
485 XCTAssertNil(error, "No error loading engram tlk from piggy contents");
486
487 [self.autoUnlockZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
488 XCTAssertNil(error, "No error loading AutoUnlock tlk from piggy contents");
489
490 [self.healthZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
491 XCTAssertNil(error, "No error loading Health tlk from piggy contents");
492
493 [self.applepayZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
494 XCTAssertNil(error, "No error loading ApplePay tlk from piggy contents");
495
496 [self.homeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
497 XCTAssertNil(error, "No error loading Home tlk from piggy contents");
498 }
499
500 -(NSString*)fileForStorage
501 {
502 static dispatch_once_t onceToken;
503 static NSString *tempPath = NULL;
504 dispatch_once(&onceToken, ^{
505 tempPath = [[[[NSFileManager defaultManager] temporaryDirectory] URLByAppendingPathComponent:@"PiggyPacket"] path];
506
507 });
508 return tempPath;
509 }
510
511 -(void)testPiggybackingData{
512 [self putFakeKeyHierachiesInCloudKit];
513 [self saveTLKsToKeychain];
514
515 for(CKRecordZoneID* zoneID in self.ckksZones) {
516 [self expectCKKSTLKSelfShareUpload:zoneID];
517 }
518 [self startCKKSSubsystem];
519
520 [self waitForKeyHierarchyReadinesses];
521
522 OCMVerifyAllWithDelay(self.mockDatabase, 20);
523
524 /*
525 * Pull data from keychain and view manager
526 */
527
528 NSDictionary* piggydata = [self SOSPiggyBackCopyFromKeychain];
529 NSArray<NSData *>* icloudidentities = piggydata[@"idents"];
530 NSArray<NSDictionary *>* tlks = piggydata[@"tlk"];
531
532 XCTAssertEqual([tlks count], [[self.injectedManager viewList] count], "TLKs not same as views");
533
534 XCTAssertNotNil(tlks, "tlks not set");
535 XCTAssertNotEqual([tlks count], (NSUInteger)0, "0 tlks");
536 XCTAssertNotNil(icloudidentities, "idents not set");
537 XCTAssertNotEqual([icloudidentities count], (NSUInteger)0, "0 icloudidentities");
538
539 NSData *initial = SOSPiggyCreateInitialSyncData(icloudidentities, tlks);
540
541 XCTAssertNotNil(initial, "Initial not set");
542 BOOL writeStatus = [initial writeToFile:[self fileForStorage] options:NSDataWritingAtomic error: nil];
543 XCTAssertTrue(writeStatus, "had trouble writing to disk");
544 XCTAssertNotEqual((int)[initial length], 0, "initial sync data is greater than 0");
545
546 /*
547 * Check that they make it accross
548 */
549
550 const uint8_t* der = [initial bytes];
551 const uint8_t *der_end = der + [initial length];
552
553 NSDictionary *result = SOSPiggyCopyInitialSyncData(&der, der_end);
554 XCTAssertNotNil(result, "Initial not set");
555 NSArray *copiedTLKs = result[@"tlks"];
556 XCTAssertNotNil(copiedTLKs, "tlks not set");
557 XCTAssertEqual([copiedTLKs count], 5u, "piggybacking should have gotten 5 TLKs across (but we have more than that elsewhere)");
558
559 NSArray *copiediCloudidentities = result[@"idents"];
560 XCTAssertNotNil(copiediCloudidentities, "idents not set");
561 XCTAssertEqual([copiediCloudidentities count], [icloudidentities count], "ident count not same");
562 }
563
564 -(void)testVerifyTLKSorting {
565 char key[32*2] = {0};
566 NSArray<NSDictionary *> *tlks = @[
567 @{
568 @"acct" : @"11111111",
569 @"srvr" : @"Manatee",
570 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
571 @"auth" : @YES,
572 },
573 @{
574 @"acct" : @"55555555",
575 @"srvr" : @"Health",
576 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
577 },
578 @{
579 @"acct" : @"22222222",
580 @"srvr" : @"Engram",
581 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
582 @"auth" : @YES,
583 },
584 @{
585 @"acct" : @"44444444",
586 @"srvr" : @"Manatee",
587 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
588 },
589 @{
590 @"acct" : @"33333333",
591 @"srvr" : @"Health",
592 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
593 @"auth" : @YES,
594 },
595 @{
596 @"acct" : @"66666666",
597 @"srvr" : @"Home",
598 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
599 @"auth" : @YES,
600 },
601 ];
602
603 NSArray<NSDictionary *>* sortedTLKs = SOSAccountSortTLKS(tlks);
604 XCTAssertNotNil(sortedTLKs, "sortedTLKs not set");
605
606 // Home gets sorted into the middle, as the other Health and Manatee TLKs aren't 'authoritative'
607 NSArray<NSString *> *expectedOrder = @[ @"11111111", @"22222222", @"33333333", @"66666666", @"44444444", @"55555555"];
608 [sortedTLKs enumerateObjectsUsingBlock:^(NSDictionary *tlk, NSUInteger idx, BOOL * _Nonnull stop) {
609 NSString *uuid = tlk[@"acct"];
610 XCTAssertEqualObjects(uuid, expectedOrder[idx], "wrong order");
611 }];
612 }
613
614
615 - (void)testAcceptExistingPiggyKeyHierarchy {
616 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
617 // Test also begins with the TLK having arrived in the local keychain (via SOS)
618 [self putFakeKeyHierachiesInCloudKit];
619 [self saveTLKsToKeychain];
620 NSDictionary* piggyTLKS = [self SOSPiggyBackCopyFromKeychain];
621 [self SOSPiggyBackAddToKeychain:piggyTLKS];
622 [self deleteTLKMaterialsFromKeychain];
623
624 // The CKKS subsystem should write a TLK Share for each view
625 for(CKRecordZoneID* zoneID in self.ckksZones) {
626 [self expectCKKSTLKSelfShareUpload:zoneID];
627 }
628
629 // Spin up CKKS subsystem.
630 [self startCKKSSubsystem];
631
632 [self.manateeView waitForKeyHierarchyReadiness];
633
634 OCMVerifyAllWithDelay(self.mockDatabase, 20);
635
636 // Verify that there are three local keys, and three local current key records
637 __weak __typeof(self) weakSelf = self;
638 [self.manateeView dispatchSync: ^bool{
639 __strong __typeof(weakSelf) strongSelf = weakSelf;
640 XCTAssertNotNil(strongSelf, "self exists");
641
642 NSError* error = nil;
643
644 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.manateeZoneID error:&error];
645 XCTAssertNil(error, "no error fetching keys");
646 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
647
648 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:strongSelf.manateeZoneID error:&error];
649 XCTAssertNil(error, "no error fetching current keys");
650 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
651
652 // Ensure that the manatee syncable TLK is created from a piggy
653 NSDictionary* query = @{
654 (id)kSecClass : (id)kSecClassInternetPassword,
655 (id)kSecAttrNoLegacy : @YES,
656 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
657 (id)kSecAttrDescription: SecCKKSKeyClassTLK,
658 (id)kSecAttrAccount: strongSelf.manateeZoneKeys.tlk.uuid,
659 (id)kSecAttrServer: strongSelf.manateeZoneID.zoneName,
660 (id)kSecAttrSynchronizable: @YES,
661 (id)kSecReturnAttributes: @YES,
662 (id)kSecReturnData: @YES,
663 };
664 CFTypeRef result = nil;
665 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), "Found a syncable TLK");
666 XCTAssertNotNil((__bridge id) result, "Received a result from SecItemCopyMatching");
667 CFReleaseNull(result);
668
669 return false;
670 }];
671 }
672
673 - (void)putFakeDeviceStatusesInCloudKit {
674 [self putFakeDeviceStatusInCloudKit: self.engramZoneID];
675 [self putFakeDeviceStatusInCloudKit: self.manateeZoneID];
676 [self putFakeDeviceStatusInCloudKit: self.autoUnlockZoneID];
677 [self putFakeDeviceStatusInCloudKit: self.healthZoneID];
678 [self putFakeDeviceStatusInCloudKit: self.applepayZoneID];
679 [self putFakeDeviceStatusInCloudKit: self.homeZoneID];
680 [self putFakeDeviceStatusInCloudKit: self.limitedZoneID];
681 }
682
683 -(void)putFakeKeyHierachiesInCloudKit{
684 [self putFakeKeyHierarchyInCloudKit: self.engramZoneID];
685 [self putFakeKeyHierarchyInCloudKit: self.manateeZoneID];
686 [self putFakeKeyHierarchyInCloudKit: self.autoUnlockZoneID];
687 [self putFakeKeyHierarchyInCloudKit: self.healthZoneID];
688 [self putFakeKeyHierarchyInCloudKit: self.applepayZoneID];
689 [self putFakeKeyHierarchyInCloudKit: self.homeZoneID];
690 [self putFakeKeyHierarchyInCloudKit: self.limitedZoneID];
691 }
692 -(void)saveTLKsToKeychain{
693 [self saveTLKMaterialToKeychain:self.engramZoneID];
694 [self saveTLKMaterialToKeychain:self.manateeZoneID];
695 [self saveTLKMaterialToKeychain:self.autoUnlockZoneID];
696 [self saveTLKMaterialToKeychain:self.healthZoneID];
697 [self saveTLKMaterialToKeychain:self.applepayZoneID];
698 [self saveTLKMaterialToKeychain:self.homeZoneID];
699 [self saveTLKMaterialToKeychain:self.limitedZoneID];
700 }
701 -(void)deleteTLKMaterialsFromKeychain{
702 [self deleteTLKMaterialFromKeychain: self.engramZoneID];
703 [self deleteTLKMaterialFromKeychain: self.manateeZoneID];
704 [self deleteTLKMaterialFromKeychain: self.autoUnlockZoneID];
705 [self deleteTLKMaterialFromKeychain: self.healthZoneID];
706 [self deleteTLKMaterialFromKeychain: self.applepayZoneID];
707 [self deleteTLKMaterialFromKeychain: self.homeZoneID];
708 [self deleteTLKMaterialFromKeychain:self.limitedZoneID];
709 }
710
711 -(void)waitForKeyHierarchyReadinesses {
712 [self.manateeView waitForKeyHierarchyReadiness];
713 [self.engramView waitForKeyHierarchyReadiness];
714 [self.autoUnlockView waitForKeyHierarchyReadiness];
715 [self.healthView waitForKeyHierarchyReadiness];
716 [self.applepayView waitForKeyHierarchyReadiness];
717 [self.homeView waitForKeyHierarchyReadiness];
718 [self.limitedView waitForKeyHierarchyReadiness];
719 }
720
721 -(void)testAcceptExistingAndUsePiggyKeyHierarchy {
722 // Test starts with nothing in database, but one in our fake CloudKit.
723 [self putFakeKeyHierachiesInCloudKit];
724 [self putFakeDeviceStatusesInCloudKit];
725 [self saveTLKsToKeychain];
726 NSDictionary* piggyData = [self SOSPiggyBackCopyFromKeychain];
727 [self deleteTLKMaterialsFromKeychain];
728
729 // Spin up CKKS subsystem.
730 [self startCKKSSubsystem];
731
732 // The CKKS subsystem should not try to write anything to the CloudKit database.
733 XCTAssertEqual(0, [self.manateeView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "CKKS entered waitfortlk");
734
735 OCMVerifyAllWithDelay(self.mockDatabase, 20);
736
737 // Now, save the TLKs to the keychain (to simulate them coming in later via piggybacking).
738 for(CKRecordZoneID* zoneID in self.ckksZones) {
739 [self expectCKKSTLKSelfShareUpload:zoneID];
740 }
741
742 [self SOSPiggyBackAddToKeychain:piggyData];
743 [self waitForKeyHierarchyReadinesses];
744
745 // We expect a single record to be uploaded for each key class
746 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassCBlock:self.manateeZoneID message:@"Object was encrypted under class C key in hierarchy"]];
747 [self addGenericPassword: @"data" account: @"account-delete-me-manatee" viewHint:(id)kSecAttrViewHintManatee];
748
749 OCMVerifyAllWithDelay(self.mockDatabase, 20);
750
751 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassABlock:self.manateeZoneID message:@"Object was encrypted under class A key in hierarchy"]];
752
753 [self addGenericPassword:@"asdf"
754 account:@"account-class-A"
755 viewHint:(id)kSecAttrViewHintManatee
756 access:(id)kSecAttrAccessibleWhenUnlocked
757 expecting:errSecSuccess
758 message:@"Adding class A item"];
759 OCMVerifyAllWithDelay(self.mockDatabase, 20);
760 }
761 @end
762
763 #endif // OCTAGON
764