2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 #import <CloudKit/CloudKit.h>
27 #import <XCTest/XCTest.h>
28 #import <OCMock/OCMock.h>
30 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
31 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
32 #import "keychain/ckks/CKKS.h"
33 #import "keychain/ckks/CKKSKey.h"
34 #import "keychain/ckks/CKKSPeer.h"
35 #import "keychain/ckks/CKKSTLKShare.h"
36 #import "keychain/ckks/CKKSViewManager.h"
38 #import "keychain/ckks/tests/MockCloudKit.h"
39 #import "keychain/ckks/tests/CKKSTests.h"
41 @interface CloudKitKeychainSyncingTLKSharingTests : CloudKitKeychainSyncingTestsBase
42 @property CKKSSOSSelfPeer* remotePeer1;
43 @property CKKSSOSPeer* remotePeer2;
46 @property CKKSSOSSelfPeer* untrustedPeer;
49 @implementation CloudKitKeychainSyncingTLKSharingTests
53 SecCKKSSetShareTLKs(true);
55 self.remotePeer1 = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"remote-peer1"
56 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
57 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]];
59 self.remotePeer2 = [[CKKSSOSPeer alloc] initWithSOSPeerID:@"remote-peer2"
60 encryptionPublicKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]].publicKey
61 signingPublicKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]].publicKey];
63 // Local SOS trusts these peers
64 [self.currentPeers addObject:self.remotePeer1];
65 [self.currentPeers addObject:self.remotePeer2];
67 self.untrustedPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"untrusted-peer"
68 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
69 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]];
73 self.remotePeer1 = nil;
74 self.remotePeer2 = nil;
75 self.untrustedPeer = nil;
79 SecCKKSSetShareTLKs(false);
82 - (void)testAcceptExistingTLKSharedKeyHierarchy {
83 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
84 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
86 // Test also starts with the TLK shared to all trusted peers from peer1
87 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
89 // The CKKS subsystem should accept the keys, and share the TLK back to itself
90 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
91 [self startCKKSSubsystem];
92 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
94 OCMVerifyAllWithDelay(self.mockDatabase, 8);
96 // Verify that there are three local keys, and three local current key records
97 __weak __typeof(self) weakSelf = self;
98 [self.keychainView dispatchSync: ^bool{
99 __strong __typeof(weakSelf) strongSelf = weakSelf;
100 XCTAssertNotNil(strongSelf, "self exists");
102 NSError* error = nil;
104 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.keychainZoneID error:&error];
105 XCTAssertNil(error, "no error fetching keys");
106 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
108 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:&error];
109 XCTAssertNil(error, "no error fetching current keys");
110 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
116 - (void)testAcceptExistingTLKSharedKeyHierarchyAndUse {
117 // Test starts with nothing in database, but one in our fake CloudKit.
118 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
120 // Test also starts with the TLK shared to all trusted peers from peer1
121 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
123 // The CKKS subsystem should accept the keys, and share the TLK back to itself
124 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
125 [self startCKKSSubsystem];
126 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
128 // We expect a single record to be uploaded for each key class
129 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
130 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
131 [self addGenericPassword: @"data" account: @"account-delete-me"];
132 OCMVerifyAllWithDelay(self.mockDatabase, 8);
134 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
135 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
136 [self addGenericPassword:@"asdf"
137 account:@"account-class-A"
139 access:(id)kSecAttrAccessibleWhenUnlocked
140 expecting:errSecSuccess
141 message:@"Adding class A item"];
142 OCMVerifyAllWithDelay(self.mockDatabase, 8);
145 - (void)testNewTLKSharesHaveChangeTags {
146 // Since there's currently no flow for CKKS to ever update a TLK share when things are working properly, do some hackery
148 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
149 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
151 // Test also starts with the TLK shared to all trusted peers from peer1
152 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
153 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
155 // The CKKS subsystem should accept the keys, and share the TLK back to itself
156 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
157 [self startCKKSSubsystem];
158 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
160 OCMVerifyAllWithDelay(self.mockDatabase, 8);
161 [self waitForCKModifications];
163 // Verify that making a new share will have the old share's change tag
164 __weak __typeof(self) weakSelf = self;
165 [self.keychainView dispatchSyncWithAccountKeys: ^bool{
166 __strong __typeof(weakSelf) strongSelf = weakSelf;
167 XCTAssertNotNil(strongSelf, "self exists");
169 NSError* error = nil;
170 CKKSTLKShare* share = [CKKSTLKShare share:strongSelf.keychainZoneKeys.tlk
171 as:strongSelf.currentSelfPeer
172 to:strongSelf.currentSelfPeer
176 XCTAssertNil(error, "Shouldn't be an error creating a share");
177 XCTAssertNotNil(share, "Should be able to create share");
179 CKRecord* newRecord = [share CKRecordWithZoneID:strongSelf.keychainZoneID];
180 XCTAssertNotNil(newRecord, "Should be able to create a CKRecord");
182 CKRecord* cloudKitRecord = strongSelf.keychainZone.currentDatabase[newRecord.recordID];
183 XCTAssertNotNil(cloudKitRecord, "Should have found existing CKRecord in cloudkit");
184 XCTAssertNotNil(cloudKitRecord.recordChangeTag, "Existing record should have a change tag");
186 XCTAssertEqualObjects(cloudKitRecord.recordChangeTag, newRecord.recordChangeTag, "Change tags on existing and new records should match");
192 - (void)testReceiveTLKShareRecordsAndDeletes {
193 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
194 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
196 // Test also starts with the TLK shared to all trusted peers from peer1
197 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
199 // The CKKS subsystem should accept the keys, and share the TLK back to itself
200 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
201 [self startCKKSSubsystem];
202 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
204 // The CKKS subsystem should not try to write anything to the CloudKit database while it's accepting the keys
205 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
206 OCMVerifyAllWithDelay(self.mockDatabase, 8);
209 // Make another share, but from an untrusted peer to some other peer. local shouldn't necessarily care.
210 NSError* error = nil;
211 CKKSTLKShare* share = [CKKSTLKShare share:self.keychainZoneKeys.tlk
212 as:self.untrustedPeer
217 XCTAssertNil(error, "Should have been no error sharing a CKKSKey");
218 XCTAssertNotNil(share, "Should be able to create a share");
220 CKRecord* shareCKRecord = [share CKRecordWithZoneID: self.keychainZoneID];
221 XCTAssertNotNil(shareCKRecord, "Should have been able to create a CKRecord");
222 [self.keychainZone addToZone:shareCKRecord];
223 [self.keychainView notifyZoneChange:nil];
224 [self.keychainView waitForFetchAndIncomingQueueProcessing];
226 [self.keychainView dispatchSync:^bool {
227 NSError* blockerror = nil;
228 CKKSTLKShare* localshare = [CKKSTLKShare tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
229 XCTAssertNil(blockerror, "Shouldn't error finding TLKShare record in database");
230 XCTAssertNotNil(localshare, "Should be able to find a TLKShare record in database");
234 // Delete the record in CloudKit...
235 [self.keychainZone deleteCKRecordIDFromZone:shareCKRecord.recordID];
236 [self.keychainView notifyZoneChange:nil];
237 [self.keychainView waitForFetchAndIncomingQueueProcessing];
239 // Should be gone now.
240 [self.keychainView dispatchSync:^bool {
241 NSError* blockerror = nil;
242 CKKSTLKShare* localshare = [CKKSTLKShare tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
244 XCTAssertNil(blockerror, "Shouldn't error trying to find non-existent TLKShare record in database");
245 XCTAssertNil(localshare, "Shouldn't be able to find a TLKShare record in database");
251 - (void)testReceiveSharedTLKWhileInWaitForTLK {
252 // Test starts with nothing in database, but one in our fake CloudKit.
253 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
255 // Spin up CKKS subsystem.
256 [self startCKKSSubsystem];
258 // The CKKS subsystem should not try to write anything to the CloudKit database, but it should enter waitfortlk
259 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
261 // peer1 arrives to save the day
262 // The CKKS subsystem should accept the keys, and share the TLK back to itself
263 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
265 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
266 [self.keychainView notifyZoneChange:nil];
267 [self.keychainView waitForFetchAndIncomingQueueProcessing];
269 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
271 // We expect a single record to be uploaded for each key class
272 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
273 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
274 [self addGenericPassword: @"data" account: @"account-delete-me"];
275 OCMVerifyAllWithDelay(self.mockDatabase, 8);
277 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
278 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
279 [self addGenericPassword:@"asdf"
280 account:@"account-class-A"
282 access:(id)kSecAttrAccessibleWhenUnlocked
283 expecting:errSecSuccess
284 message:@"Adding class A item"];
285 OCMVerifyAllWithDelay(self.mockDatabase, 8);
288 - (void)testReceiveTLKShareWhileLocked {
289 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
290 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
292 // Test also starts with the TLK shared to all trusted peers from peer1
293 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
295 // Because 33710924 didn't make it backwards in time, this test is fragile on Chipmunk/Cinar.
296 self.aksLockState = true;
297 [self.lockStateTracker recheck];
299 // Spin up CKKS subsystem.
300 [self startCKKSSubsystem];
302 // The CKKS subsystem should not try to write anything to the CloudKit database, but it should enter waitforunlock
303 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForUnlock] wait:10*NSEC_PER_SEC], "Key state should become waitforunlock");
305 // Now unlock things. We expect a TLKShare upload.
306 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
308 self.aksLockState = false;
309 [self.lockStateTracker recheck];
311 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
312 OCMVerifyAllWithDelay(self.mockDatabase, 8);
315 - (void)testUploadTLKSharesForExistingHierarchy {
316 // Test starts with key material locally and in CloudKit.
317 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
318 [self saveTLKMaterialToKeychain:self.keychainZoneID];
320 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
321 [self startCKKSSubsystem];
323 OCMVerifyAllWithDelay(self.mockDatabase, 8);
326 - (void)testUploadTLKSharesForExistingHierarchyOnRestart {
327 // Turn off TLK sharing, and get situated
328 SecCKKSSetShareTLKs(false);
330 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
331 [self saveTLKMaterialToKeychain:self.keychainZoneID];
332 [self startCKKSSubsystem];
334 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
336 // Turn TLK sharing back on, and restart. We expect an upload of 3 TLK shares.
337 SecCKKSSetShareTLKs(true);
338 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
339 self.keychainView = [self.injectedManager restartZone: self.keychainZoneID.zoneName];
341 OCMVerifyAllWithDelay(self.mockDatabase, 8);
342 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
345 - (void)testHandleExternalSharedTLKRoll {
346 // Test starts with key material locally and in CloudKit.
347 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
348 [self saveTLKMaterialToKeychain:self.keychainZoneID];
350 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
351 [self startCKKSSubsystem];
353 OCMVerifyAllWithDelay(self.mockDatabase, 8);
355 // Now the external peer rolls the TLK and updates the shares
356 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
357 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
359 // CKKS will share the TLK back to itself
360 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
362 // Trigger a notification
363 [self.keychainView notifyZoneChange:nil];
364 [self.keychainView waitForFetchAndIncomingQueueProcessing];
365 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
366 OCMVerifyAllWithDelay(self.mockDatabase, 8);
367 [self waitForCKModifications];
369 // We expect a single record to be uploaded.
370 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
371 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
372 [self addGenericPassword: @"data" account: @"account-delete-me-rolled-key"];
374 OCMVerifyAllWithDelay(self.mockDatabase, 8);
377 - (void)testUploadTLKSharesForExternalTLKRollWithoutShares {
378 // Test starts with key material locally and in CloudKit.
379 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
380 [self saveTLKMaterialToKeychain:self.keychainZoneID];
382 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
383 [self startCKKSSubsystem];
385 OCMVerifyAllWithDelay(self.mockDatabase, 8);
387 // Now, an old (Tigris) peer rolls the TLK, but doesn't share it
388 // CKKS should get excited and throw 3 new share records up
389 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
391 // Wait for that modification to finish before changing CK data
392 [self waitForCKModifications];
394 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
395 [self saveTLKMaterialToKeychain:self.keychainZoneID];
397 // Trigger a notification
398 [self.keychainView notifyZoneChange:nil];
400 OCMVerifyAllWithDelay(self.mockDatabase, 8);
401 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
403 // We expect a single record to be uploaded.
404 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
405 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
406 [self addGenericPassword: @"data" account: @"account-delete-me-rolled-key"];
408 OCMVerifyAllWithDelay(self.mockDatabase, 8);
411 - (void)testRecoverFromTLKShareUploadFailure {
412 // Test starts with key material locally and in CloudKit.
413 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
414 [self saveTLKMaterialToKeychain:self.keychainZoneID];
416 __weak __typeof(self) weakSelf = self;
417 [self failNextCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID blockAfterReject:^{
418 __strong __typeof(self) strongSelf = weakSelf;
419 [strongSelf expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
421 [self startCKKSSubsystem];
423 OCMVerifyAllWithDelay(self.mockDatabase, 8);
424 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
427 - (void)testFillInMissingPeerShares {
428 // Test starts with nothing in database, but one in our fake CloudKit.
429 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
431 // Test also starts with the TLK shared to just the local peer from peer1
432 // We expect the local peer to send it to peer2
433 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
435 // The CKKS subsystem should accept the keys, and share the TLK back to itself
436 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
437 [self startCKKSSubsystem];
438 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
440 // We expect a single record to be uploaded for each key class
441 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
442 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
443 [self addGenericPassword: @"data" account: @"account-delete-me"];
444 OCMVerifyAllWithDelay(self.mockDatabase, 8);
446 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
447 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
448 [self addGenericPassword:@"asdf"
449 account:@"account-class-A"
451 access:(id)kSecAttrAccessibleWhenUnlocked
452 expecting:errSecSuccess
453 message:@"Adding class A item"];
454 OCMVerifyAllWithDelay(self.mockDatabase, 8);
457 - (void)testDontAcceptTLKFromUntrustedPeer {
458 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
459 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
461 // Test also starts with the key hierarchy shared from a non-trusted peer
462 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer zoneID:self.keychainZoneID];
464 // The CKKS subsystem should go into waitfortlk, since it doesn't trust this peer
465 [self startCKKSSubsystem];
466 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become ready");
469 - (void)testAcceptSharedTLKOnTrustSetAdditionOfSharer {
470 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
471 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
473 // Test also starts with the key hierarchy shared from a non-trusted peer
474 // note that it would share it itself too
475 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer zoneID:self.keychainZoneID];
476 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer to:self.untrustedPeer zoneID:self.keychainZoneID];
478 // The CKKS subsystem should go into waitfortlk, since it doesn't trust this peer
479 [self startCKKSSubsystem];
480 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
482 // Wait to be sure we really get into that state
483 [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
485 // Now, trust the previously-untrusted peer
486 [self.currentPeers addObject: self.untrustedPeer];
487 [self.injectedManager sendTrustedPeerSetChangedUpdate];
489 // The CKKS subsystem should now accept the key, and share the TLK back to itself
490 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
492 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:500*NSEC_PER_SEC], "Key state should become ready");
494 // And use it as well
495 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
496 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
497 [self addGenericPassword: @"data" account: @"account-delete-me"];
498 OCMVerifyAllWithDelay(self.mockDatabase, 8);
501 - (void)testSendNewTLKSharesOnTrustSetAddition {
502 // step 1: add a new peer; we should share the TLK with them
503 // start with no trusted peers
504 [self.currentPeers removeAllObjects];
506 [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
507 [self startCKKSSubsystem];
509 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
510 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
511 [self addGenericPassword: @"data" account: @"account-delete-me"];
512 OCMVerifyAllWithDelay(self.mockDatabase, 8);
514 // Cool! New peer arrives!
515 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
516 [self.currentPeers addObject:self.remotePeer1];
517 [self.injectedManager sendTrustedPeerSetChangedUpdate];
519 OCMVerifyAllWithDelay(self.mockDatabase, 8);
520 [self waitForCKModifications];
522 // step 2: add a new peer who already has a share; no share should be created
523 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 to:self.remotePeer2 zoneID:self.keychainZoneID];
524 [self.keychainView notifyZoneChange:nil];
525 [self.keychainView waitForFetchAndIncomingQueueProcessing];
527 // CKKS should not upload a tlk share for this peer
528 [self.currentPeers addObject:self.remotePeer2];
529 [self.injectedManager sendTrustedPeerSetChangedUpdate];
531 [self.keychainView waitUntilAllOperationsAreFinished];
534 - (void)testSendNewTLKSharesOnTrustSetRemoval {
535 // Not implemented. Trust set removal demands a key roll, but let's not get ahead of ourselves...