]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSTLKSharingTests.m
Security-58286.31.2.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSTLKSharingTests.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
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"
37
38 #import "keychain/ckks/tests/MockCloudKit.h"
39 #import "keychain/ckks/tests/CKKSTests.h"
40
41 @interface CloudKitKeychainSyncingTLKSharingTests : CloudKitKeychainSyncingTestsBase
42 @property CKKSSOSSelfPeer* remotePeer1;
43 @property CKKSSOSPeer* remotePeer2;
44
45
46 @property CKKSSOSSelfPeer* untrustedPeer;
47 @end
48
49 @implementation CloudKitKeychainSyncingTLKSharingTests
50
51 - (void)setUp {
52 [super setUp];
53 SecCKKSSetShareTLKs(true);
54
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]]];
58
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];
62
63 // Local SOS trusts these peers
64 [self.currentPeers addObject:self.remotePeer1];
65 [self.currentPeers addObject:self.remotePeer2];
66
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]]];
70 }
71
72 - (void)tearDown {
73 self.remotePeer1 = nil;
74 self.remotePeer2 = nil;
75 self.untrustedPeer = nil;
76
77 [super tearDown];
78
79 SecCKKSSetShareTLKs(false);
80 }
81
82 - (void)testAcceptExistingTLKSharedKeyHierarchy {
83 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
84 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
85
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];
88
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");
93
94 OCMVerifyAllWithDelay(self.mockDatabase, 8);
95
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");
101
102 NSError* error = nil;
103
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");
107
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");
111
112 return false;
113 }];
114 }
115
116 - (void)testAcceptExistingTLKSharedKeyHierarchyAndUse {
117 // Test starts with nothing in database, but one in our fake CloudKit.
118 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
119
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];
122
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");
127
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);
133
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"
138 viewHint:nil
139 access:(id)kSecAttrAccessibleWhenUnlocked
140 expecting:errSecSuccess
141 message:@"Adding class A item"];
142 OCMVerifyAllWithDelay(self.mockDatabase, 8);
143 }
144
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
147
148 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
149 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
150
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];
154
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");
159
160 OCMVerifyAllWithDelay(self.mockDatabase, 8);
161 [self waitForCKModifications];
162
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");
168
169 NSError* error = nil;
170 CKKSTLKShare* share = [CKKSTLKShare share:strongSelf.keychainZoneKeys.tlk
171 as:strongSelf.currentSelfPeer
172 to:strongSelf.currentSelfPeer
173 epoch:-1
174 poisoned:0
175 error:&error];
176 XCTAssertNil(error, "Shouldn't be an error creating a share");
177 XCTAssertNotNil(share, "Should be able to create share");
178
179 CKRecord* newRecord = [share CKRecordWithZoneID:strongSelf.keychainZoneID];
180 XCTAssertNotNil(newRecord, "Should be able to create a CKRecord");
181
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");
185
186 XCTAssertEqualObjects(cloudKitRecord.recordChangeTag, newRecord.recordChangeTag, "Change tags on existing and new records should match");
187
188 return false;
189 }];
190 }
191
192 - (void)testReceiveTLKShareRecordsAndDeletes {
193 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
194 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
195
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];
198
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");
203
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);
207
208
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
213 to:self.remotePeer1
214 epoch:-1
215 poisoned:0
216 error:&error];
217 XCTAssertNil(error, "Should have been no error sharing a CKKSKey");
218 XCTAssertNotNil(share, "Should be able to create a share");
219
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];
225
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");
231 return true;
232 }];
233
234 // Delete the record in CloudKit...
235 [self.keychainZone deleteCKRecordIDFromZone:shareCKRecord.recordID];
236 [self.keychainView notifyZoneChange:nil];
237 [self.keychainView waitForFetchAndIncomingQueueProcessing];
238
239 // Should be gone now.
240 [self.keychainView dispatchSync:^bool {
241 NSError* blockerror = nil;
242 CKKSTLKShare* localshare = [CKKSTLKShare tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
243
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");
246
247 return true;
248 }];
249 }
250
251 - (void)testReceiveSharedTLKWhileInWaitForTLK {
252 // Test starts with nothing in database, but one in our fake CloudKit.
253 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
254
255 // Spin up CKKS subsystem.
256 [self startCKKSSubsystem];
257
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");
260
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];
264
265 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
266 [self.keychainView notifyZoneChange:nil];
267 [self.keychainView waitForFetchAndIncomingQueueProcessing];
268
269 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
270
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);
276
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"
281 viewHint:nil
282 access:(id)kSecAttrAccessibleWhenUnlocked
283 expecting:errSecSuccess
284 message:@"Adding class A item"];
285 OCMVerifyAllWithDelay(self.mockDatabase, 8);
286 }
287
288 - (void)testReceiveTLKShareWhileLocked {
289 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
290 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
291
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];
294
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];
298
299 // Spin up CKKS subsystem.
300 [self startCKKSSubsystem];
301
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");
304
305 // Now unlock things. We expect a TLKShare upload.
306 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
307
308 self.aksLockState = false;
309 [self.lockStateTracker recheck];
310
311 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
312 OCMVerifyAllWithDelay(self.mockDatabase, 8);
313 }
314
315 - (void)testUploadTLKSharesForExistingHierarchy {
316 // Test starts with key material locally and in CloudKit.
317 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
318 [self saveTLKMaterialToKeychain:self.keychainZoneID];
319
320 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
321 [self startCKKSSubsystem];
322
323 OCMVerifyAllWithDelay(self.mockDatabase, 8);
324 }
325
326 - (void)testUploadTLKSharesForExistingHierarchyOnRestart {
327 // Turn off TLK sharing, and get situated
328 SecCKKSSetShareTLKs(false);
329
330 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
331 [self saveTLKMaterialToKeychain:self.keychainZoneID];
332 [self startCKKSSubsystem];
333
334 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
335
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];
340
341 OCMVerifyAllWithDelay(self.mockDatabase, 8);
342 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
343 }
344
345 - (void)testHandleExternalSharedTLKRoll {
346 // Test starts with key material locally and in CloudKit.
347 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
348 [self saveTLKMaterialToKeychain:self.keychainZoneID];
349
350 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
351 [self startCKKSSubsystem];
352
353 OCMVerifyAllWithDelay(self.mockDatabase, 8);
354
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];
358
359 // CKKS will share the TLK back to itself
360 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
361
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];
368
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"];
373
374 OCMVerifyAllWithDelay(self.mockDatabase, 8);
375 }
376
377 - (void)testUploadTLKSharesForExternalTLKRollWithoutShares {
378 // Test starts with key material locally and in CloudKit.
379 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
380 [self saveTLKMaterialToKeychain:self.keychainZoneID];
381
382 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
383 [self startCKKSSubsystem];
384
385 OCMVerifyAllWithDelay(self.mockDatabase, 8);
386
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];
390
391 // Wait for that modification to finish before changing CK data
392 [self waitForCKModifications];
393
394 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
395 [self saveTLKMaterialToKeychain:self.keychainZoneID];
396
397 // Trigger a notification
398 [self.keychainView notifyZoneChange:nil];
399
400 OCMVerifyAllWithDelay(self.mockDatabase, 8);
401 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
402
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"];
407
408 OCMVerifyAllWithDelay(self.mockDatabase, 8);
409 }
410
411 - (void)testRecoverFromTLKShareUploadFailure {
412 // Test starts with key material locally and in CloudKit.
413 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
414 [self saveTLKMaterialToKeychain:self.keychainZoneID];
415
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];
420 }];
421 [self startCKKSSubsystem];
422
423 OCMVerifyAllWithDelay(self.mockDatabase, 8);
424 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
425 }
426
427 - (void)testFillInMissingPeerShares {
428 // Test starts with nothing in database, but one in our fake CloudKit.
429 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
430
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];
434
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");
439
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);
445
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"
450 viewHint:nil
451 access:(id)kSecAttrAccessibleWhenUnlocked
452 expecting:errSecSuccess
453 message:@"Adding class A item"];
454 OCMVerifyAllWithDelay(self.mockDatabase, 8);
455 }
456
457 - (void)testDontAcceptTLKFromUntrustedPeer {
458 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
459 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
460
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];
463
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");
467 }
468
469 - (void)testAcceptSharedTLKOnTrustSetAdditionOfSharer {
470 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
471 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
472
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];
477
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");
481
482 // Wait to be sure we really get into that state
483 [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
484
485 // Now, trust the previously-untrusted peer
486 [self.currentPeers addObject: self.untrustedPeer];
487 [self.injectedManager sendTrustedPeerSetChangedUpdate];
488
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];
491
492 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:500*NSEC_PER_SEC], "Key state should become ready");
493
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);
499 }
500
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];
505
506 [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
507 [self startCKKSSubsystem];
508
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);
513
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];
518
519 OCMVerifyAllWithDelay(self.mockDatabase, 8);
520 [self waitForCKModifications];
521
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];
526
527 // CKKS should not upload a tlk share for this peer
528 [self.currentPeers addObject:self.remotePeer2];
529 [self.injectedManager sendTrustedPeerSetChangedUpdate];
530
531 [self.keychainView waitUntilAllOperationsAreFinished];
532 }
533
534 - (void)testSendNewTLKSharesOnTrustSetRemoval {
535 // Not implemented. Trust set removal demands a key roll, but let's not get ahead of ourselves...
536 }
537
538
539 @end
540
541 #endif // OCTAGON