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>
29 #import <SecurityFoundation/SFDigestOperation.h>
30 #import <SecurityFoundation/SFKey.h>
31 #import <SecurityFoundation/SFKey_Private.h>
32 #import <SecurityFoundation/SFIdentity.h>
34 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
35 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
36 #import "keychain/ckks/CKKS.h"
37 #import "keychain/ckks/CKKSKey.h"
38 #import "keychain/ckks/CKKSPeer.h"
39 #import "keychain/ckks/CKKSTLKShareRecord.h"
40 #import "keychain/ckks/CKKSViewManager.h"
41 #import "keychain/ckks/CloudKitCategories.h"
42 #import "keychain/categories/NSError+UsefulConstructors.h"
44 #import "keychain/ckks/tests/MockCloudKit.h"
45 #import "keychain/ckks/tests/CKKSTests.h"
46 #import "keychain/ot/OTDefines.h"
47 #import "keychain/ot/OctagonCKKSPeerAdapter.h"
49 #import "keychain/ckks/tests/CKKSMockOctagonAdapter.h"
51 @class OctagonSelfPeer;
53 @interface CloudKitKeychainSyncingTLKSharingTests : CloudKitKeychainSyncingTestsBase
54 @property CKKSSOSSelfPeer* remotePeer1;
55 @property CKKSSOSPeer* remotePeer2;
57 @property OctagonSelfPeer* selfOTPeer1;
58 @property id<CKKSPeer> remoteOTPeer2;
60 @property CKKSSOSSelfPeer* untrustedPeer;
62 @property (nullable) NSMutableSet<id<CKKSSelfPeer>>* pastSelfPeers;
66 @implementation CloudKitKeychainSyncingTLKSharingTests
71 self.pastSelfPeers = [NSMutableSet set];
73 self.remotePeer1 = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"remote-peer1"
74 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
75 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
76 viewList:self.managedViewList];
78 self.remotePeer2 = [[CKKSSOSPeer alloc] initWithSOSPeerID:@"remote-peer2"
79 encryptionPublicKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]].publicKey
80 signingPublicKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]].publicKey
81 viewList:self.managedViewList];
83 // Local SOS trusts these peers
84 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
85 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer2];
87 self.untrustedPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"untrusted-peer"
88 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
89 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
90 viewList:self.managedViewList];
95 SFKeyPair *signingKeyPair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
96 SFKeyPair *encrytionKeyPair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
98 self.selfOTPeer1 = [[OctagonSelfPeer alloc] initWithPeerID:@"SHA256:ot-self-peer"
99 signingIdentity:[[SFIdentity alloc] initWithKeyPair: signingKeyPair]
100 encryptionIdentity:[[SFIdentity alloc] initWithKeyPair: encrytionKeyPair]];
101 self.remoteOTPeer2 = nil;
103 self.mockOctagonAdapter = [[CKKSMockOctagonAdapter alloc] initWithSelfPeer:self.selfOTPeer1
104 trustedPeers:[NSSet set]
106 self.mockOctagonAdapter.selfViewList = self.managedViewList;
111 self.pastSelfPeers = nil;
112 self.remotePeer1 = nil;
113 self.remotePeer2 = nil;
114 self.untrustedPeer = nil;
115 self.selfOTPeer1 = nil;
116 self.remoteOTPeer2 = nil;
121 - (void)testAcceptExistingTLKSharedKeyHierarchy {
122 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
123 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
125 // Test also starts with the TLK shared to all trusted peers from peer1
126 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
128 // The CKKS subsystem should accept the keys, and share the TLK back to itself
129 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
130 [self startCKKSSubsystem];
131 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
133 OCMVerifyAllWithDelay(self.mockDatabase, 20);
135 // Verify that there are three local keys, and three local current key records
136 __weak __typeof(self) weakSelf = self;
137 [self.keychainView dispatchSync: ^bool{
138 __strong __typeof(weakSelf) strongSelf = weakSelf;
139 XCTAssertNotNil(strongSelf, "self exists");
141 NSError* error = nil;
143 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.keychainZoneID error:&error];
144 XCTAssertNil(error, "no error fetching keys");
145 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
147 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:&error];
148 XCTAssertNil(error, "no error fetching current keys");
149 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
155 /* As part of 48178481, we disabled CKKS 'past selves' for SOS */
156 - (void)DISABLEDtestAcceptExistingTLKSharedKeyHierarchyForPastSelf {
157 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
158 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
160 // Test also starts with the TLK shared to all trusted peers from peer1
161 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
163 // Self rolls its keys and ID...
164 [self.pastSelfPeers addObject:self.mockSOSAdapter.selfPeer];
165 self.mockSOSAdapter.selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"new-local-peer"
166 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
167 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
168 viewList:self.managedViewList];
170 // The CKKS subsystem should accept the keys, and share the TLK back to itself
171 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
172 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
173 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
174 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match current self");
175 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match current self");
176 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
179 [self startCKKSSubsystem];
180 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
182 OCMVerifyAllWithDelay(self.mockDatabase, 20);
184 // Verify that there are three local keys, and three local current key records
185 __weak __typeof(self) weakSelf = self;
186 [self.keychainView dispatchSync: ^bool{
187 __strong __typeof(weakSelf) strongSelf = weakSelf;
188 XCTAssertNotNil(strongSelf, "self exists");
190 NSError* error = nil;
192 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.keychainZoneID error:&error];
193 XCTAssertNil(error, "no error fetching keys");
194 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
196 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:&error];
197 XCTAssertNil(error, "no error fetching current keys");
198 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
204 - (void)testAcceptExistingTLKSharedKeyHierarchyAndUse {
205 // Test starts with nothing in database, but one in our fake CloudKit.
206 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
208 // Test also starts with the TLK shared to all trusted peers from peer1
209 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
211 // The CKKS subsystem should accept the keys, and share the TLK back to itself
212 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
213 [self startCKKSSubsystem];
214 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
216 // We expect a single record to be uploaded for each key class
217 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
218 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
219 [self addGenericPassword: @"data" account: @"account-delete-me"];
220 OCMVerifyAllWithDelay(self.mockDatabase, 20);
222 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
223 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
224 [self addGenericPassword:@"asdf"
225 account:@"account-class-A"
227 access:(id)kSecAttrAccessibleWhenUnlocked
228 expecting:errSecSuccess
229 message:@"Adding class A item"];
230 OCMVerifyAllWithDelay(self.mockDatabase, 20);
233 - (void)testNewTLKSharesHaveChangeTags {
234 // Since there's currently no flow for CKKS to ever update a TLK share when things are working properly, do some hackery
236 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
237 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
239 // Test also starts with the TLK shared to all trusted peers from peer1
240 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
241 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
243 // The CKKS subsystem should accept the keys, and share the TLK back to itself
244 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
245 [self startCKKSSubsystem];
246 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
248 OCMVerifyAllWithDelay(self.mockDatabase, 20);
249 [self waitForCKModifications];
251 // Verify that making a new share will have the old share's change tag
252 __weak __typeof(self) weakSelf = self;
253 [self.keychainView dispatchSyncWithAccountKeys: ^bool{
254 __strong __typeof(weakSelf) strongSelf = weakSelf;
255 XCTAssertNotNil(strongSelf, "self exists");
257 NSError* error = nil;
258 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:strongSelf.keychainZoneKeys.tlk
259 as:strongSelf.mockSOSAdapter.selfPeer
260 to:strongSelf.mockSOSAdapter.selfPeer
264 XCTAssertNil(error, "Shouldn't be an error creating a share");
265 XCTAssertNotNil(share, "Should be able to create share");
267 CKRecord* newRecord = [share CKRecordWithZoneID:strongSelf.keychainZoneID];
268 XCTAssertNotNil(newRecord, "Should be able to create a CKRecord");
270 CKRecord* cloudKitRecord = strongSelf.keychainZone.currentDatabase[newRecord.recordID];
271 XCTAssertNotNil(cloudKitRecord, "Should have found existing CKRecord in cloudkit");
272 XCTAssertNotNil(cloudKitRecord.recordChangeTag, "Existing record should have a change tag");
274 XCTAssertEqualObjects(cloudKitRecord.recordChangeTag, newRecord.recordChangeTag, "Change tags on existing and new records should match");
280 - (void)testReceiveTLKShareRecordsAndDeletes {
281 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
282 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
284 // Test also starts with the TLK shared to all trusted peers from peer1
285 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
287 // The CKKS subsystem should accept the keys, and share the TLK back to itself
288 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
289 [self startCKKSSubsystem];
290 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
292 // The CKKS subsystem should not try to write anything to the CloudKit database while it's accepting the keys
293 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
294 OCMVerifyAllWithDelay(self.mockDatabase, 20);
297 // Make another share, but from an untrusted peer to some other peer. local shouldn't necessarily care.
298 NSError* error = nil;
299 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:self.keychainZoneKeys.tlk
300 as:self.untrustedPeer
305 XCTAssertNil(error, "Should have been no error sharing a CKKSKey");
306 XCTAssertNotNil(share, "Should be able to create a share");
308 CKRecord* shareCKRecord = [share CKRecordWithZoneID: self.keychainZoneID];
309 XCTAssertNotNil(shareCKRecord, "Should have been able to create a CKRecord");
310 [self.keychainZone addToZone:shareCKRecord];
311 [self.keychainView notifyZoneChange:nil];
312 [self.keychainView waitForFetchAndIncomingQueueProcessing];
314 [self.keychainView dispatchSync:^bool {
315 NSError* blockerror = nil;
316 CKKSTLKShareRecord* localshare = [CKKSTLKShareRecord tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
317 XCTAssertNil(blockerror, "Shouldn't error finding TLKShare record in database");
318 XCTAssertNotNil(localshare, "Should be able to find a TLKShare record in database");
322 // Delete the record in CloudKit...
323 [self.keychainZone deleteCKRecordIDFromZone:shareCKRecord.recordID];
324 [self.keychainView notifyZoneChange:nil];
325 [self.keychainView waitForFetchAndIncomingQueueProcessing];
327 // Should be gone now.
328 [self.keychainView dispatchSync:^bool {
329 NSError* blockerror = nil;
330 CKKSTLKShareRecord* localshare = [CKKSTLKShareRecord tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
332 XCTAssertNil(blockerror, "Shouldn't error trying to find non-existent TLKShare record in database");
333 XCTAssertNil(localshare, "Shouldn't be able to find a TLKShare record in database");
339 - (void)testReceiveSharedTLKWhileInWaitForTLK {
340 // Test starts with nothing in database, but one in our fake CloudKit.
341 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
342 [self putFakeDeviceStatusInCloudKit:self.keychainZoneID];
344 // Spin up CKKS subsystem.
345 [self startCKKSSubsystem];
347 // The CKKS subsystem should not try to write anything to the CloudKit database, but it should enter waitfortlk
348 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
350 // peer1 arrives to save the day
351 // The CKKS subsystem should accept the keys, and share the TLK back to itself
352 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
354 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
355 [self.keychainView notifyZoneChange:nil];
356 [self.keychainView waitForFetchAndIncomingQueueProcessing];
358 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
360 // We expect a single record to be uploaded for each key class
361 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
362 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
363 [self addGenericPassword: @"data" account: @"account-delete-me"];
364 OCMVerifyAllWithDelay(self.mockDatabase, 20);
366 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
367 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
368 [self addGenericPassword:@"asdf"
369 account:@"account-class-A"
371 access:(id)kSecAttrAccessibleWhenUnlocked
372 expecting:errSecSuccess
373 message:@"Adding class A item"];
374 OCMVerifyAllWithDelay(self.mockDatabase, 20);
377 - (void)testReceiveTLKShareWhileLocked {
378 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
379 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
381 // Test also starts with the TLK shared to all trusted peers from peer1
382 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
384 self.aksLockState = true;
385 [self.lockStateTracker recheck];
387 // Spin up CKKS subsystem.
388 [self startCKKSSubsystem];
390 // The CKKS subsystem should not try to write anything to the CloudKit database, but it should enter waitforunlock
391 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForUnlock] wait:20*NSEC_PER_SEC], "Key state should become waitforunlock");
393 // Now unlock things. We expect a TLKShare upload.
394 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
396 self.aksLockState = false;
397 [self.lockStateTracker recheck];
399 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
400 OCMVerifyAllWithDelay(self.mockDatabase, 20);
403 - (void)testUploadTLKSharesForExistingHierarchy {
404 // Test starts with key material locally and in CloudKit.
405 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
406 [self saveTLKMaterialToKeychain:self.keychainZoneID];
408 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
409 [self startCKKSSubsystem];
411 OCMVerifyAllWithDelay(self.mockDatabase, 20);
414 - (void)testUploadTLKSharesForExistingHierarchyOnRestart {
415 // Bring up CKKS. It'll upload a few TLK Shares, but we'll delete them to get into state
416 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
417 [self saveTLKMaterialToKeychain:self.keychainZoneID];
419 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
420 [self startCKKSSubsystem];
422 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
423 OCMVerifyAllWithDelay(self.mockDatabase, 20);
424 [self waitForCKModifications];
426 // Now, delete all the TLK Shares, so CKKS will upload them again
427 [self.keychainView dispatchSync:^bool {
428 NSError* error = nil;
429 [CKKSTLKShareRecord deleteAll:self.keychainZoneID error:&error];
430 XCTAssertNil(error, "Shouldn't be an error deleting all TLKShares");
432 NSArray<CKRecord*>* records = [self.zones[self.keychainZoneID].currentDatabase allValues];
433 for(CKRecord* record in records) {
434 if([record.recordType isEqualToString:SecCKRecordTLKShareType]) {
435 [self.zones[self.keychainZoneID] deleteFromHistory:record.recordID];
442 // Restart. We expect an upload of 3 TLK shares.
443 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
444 self.keychainView = [self.injectedManager restartZone: self.keychainZoneID.zoneName];
445 [self beginSOSTrustedViewOperation:self.keychainView];
447 OCMVerifyAllWithDelay(self.mockDatabase, 20);
448 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
451 - (void)testHandleExternalSharedTLKRoll {
452 // Test starts with key material locally and in CloudKit.
453 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
454 [self saveTLKMaterialToKeychain:self.keychainZoneID];
456 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
457 [self startCKKSSubsystem];
459 OCMVerifyAllWithDelay(self.mockDatabase, 20);
460 [self waitForCKModifications];
462 // Now the external peer rolls the TLK and updates the shares
463 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
464 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
466 // CKKS will share the TLK back to itself
467 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
469 // Trigger a notification
470 [self.keychainView notifyZoneChange:nil];
471 [self.keychainView waitForFetchAndIncomingQueueProcessing];
472 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
473 OCMVerifyAllWithDelay(self.mockDatabase, 20);
474 [self waitForCKModifications];
476 // We expect a single record to be uploaded.
477 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
478 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
479 [self addGenericPassword: @"data" account: @"account-delete-me-rolled-key"];
481 OCMVerifyAllWithDelay(self.mockDatabase, 20);
484 - (void)testUploadTLKSharesForExternalTLKRollWithoutShares {
485 // Test starts with key material locally and in CloudKit.
486 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
487 [self saveTLKMaterialToKeychain:self.keychainZoneID];
489 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
490 [self startCKKSSubsystem];
492 OCMVerifyAllWithDelay(self.mockDatabase, 20);
494 // Now, an old (Tigris) peer rolls the TLK, but doesn't share it
495 // CKKS should get excited and throw 3 new share records up
496 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
498 // Wait for that modification to finish before changing CK data
499 [self waitForCKModifications];
501 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
502 [self saveTLKMaterialToKeychain:self.keychainZoneID];
504 // Trigger a notification
505 [self.keychainView notifyZoneChange:nil];
507 OCMVerifyAllWithDelay(self.mockDatabase, 20);
508 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
510 // We expect a single record to be uploaded.
511 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
512 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
513 [self addGenericPassword: @"data" account: @"account-delete-me-rolled-key"];
515 OCMVerifyAllWithDelay(self.mockDatabase, 20);
518 - (void)testRecoverFromTLKShareUploadFailure {
519 // Test starts with key material locally and in CloudKit.
520 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
521 [self saveTLKMaterialToKeychain:self.keychainZoneID];
523 __weak __typeof(self) weakSelf = self;
524 [self failNextCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID blockAfterReject:^{
525 __strong __typeof(self) strongSelf = weakSelf;
526 [strongSelf expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
528 [self startCKKSSubsystem];
530 OCMVerifyAllWithDelay(self.mockDatabase, 20);
531 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
534 - (void)testFillInMissingPeerShares {
535 // Test starts with nothing in database, but one in our fake CloudKit.
536 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
538 // Test also starts with the TLK shared to just the local peer from peer1
539 // We expect the local peer to send it to peer2
540 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
542 // The CKKS subsystem should accept the keys, and share the TLK back to itself
543 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
544 [self startCKKSSubsystem];
545 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
547 // We expect a single record to be uploaded for each key class
548 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
549 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
550 [self addGenericPassword: @"data" account: @"account-delete-me"];
551 OCMVerifyAllWithDelay(self.mockDatabase, 20);
553 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
554 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
555 [self addGenericPassword:@"asdf"
556 account:@"account-class-A"
558 access:(id)kSecAttrAccessibleWhenUnlocked
559 expecting:errSecSuccess
560 message:@"Adding class A item"];
561 OCMVerifyAllWithDelay(self.mockDatabase, 20);
564 - (void)testDontAcceptTLKFromUntrustedPeer {
565 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
566 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
567 // The remote peer should also have given the TLK to a non-TLKShare peer (which is also offline)
568 [self putFakeDeviceStatusInCloudKit:self.keychainZoneID];
570 // Test also starts with the key hierarchy shared from a non-trusted peer
571 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer zoneID:self.keychainZoneID];
573 // The CKKS subsystem should go into waitfortlk, since it doesn't trust this peer, but the peer is active
574 [self startCKKSSubsystem];
575 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become ready");
578 - (void)testAcceptSharedTLKOnTrustSetAdditionOfSharer {
579 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
580 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
581 [self putFakeDeviceStatusInCloudKit:self.keychainZoneID];
583 // Test also starts with the key hierarchy shared from a non-trusted peer
584 // note that it would share it itself too
585 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer zoneID:self.keychainZoneID];
586 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer to:self.untrustedPeer zoneID:self.keychainZoneID];
588 // The CKKS subsystem should go into waitfortlk, since it doesn't trust this peer, but the peer is active
589 [self startCKKSSubsystem];
590 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:2000*NSEC_PER_SEC], "Key state should become waitfortlk");
592 // Wait to be sure we really get into that state
593 [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
595 // Now, trust the previously-untrusted peer
596 [self.mockSOSAdapter.trustedPeers addObject: self.untrustedPeer];
597 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
599 // The CKKS subsystem should now accept the key, and share the TLK back to itself
600 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
602 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
604 // And use it as well
605 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
606 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
607 [self addGenericPassword: @"data" account: @"account-delete-me"];
608 OCMVerifyAllWithDelay(self.mockDatabase, 20);
611 - (void)testSendNewTLKSharesOnTrustSetAddition {
612 // step 1: add a new peer; we should share the TLK with them
613 // start with no trusted peers
614 [self.mockSOSAdapter.trustedPeers removeAllObjects];
616 [self startCKKSSubsystem];
617 [self performOctagonTLKUpload:self.ckksViews];
619 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
620 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
621 [self addGenericPassword: @"data" account: @"account-delete-me"];
622 OCMVerifyAllWithDelay(self.mockDatabase, 20);
624 // Cool! New peer arrives!
625 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
626 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
627 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
629 OCMVerifyAllWithDelay(self.mockDatabase, 20);
630 [self waitForCKModifications];
632 // step 2: add a new peer who already has a share; no share should be created
633 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 to:self.remotePeer2 zoneID:self.keychainZoneID];
634 [self.keychainView notifyZoneChange:nil];
635 [self.keychainView waitForFetchAndIncomingQueueProcessing];
637 // CKKS should not upload a tlk share for this peer
638 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer2];
639 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
641 [self.keychainView waitUntilAllOperationsAreFinished];
644 - (void)testFillInMissingPeerSharesAfterUnlock {
645 // step 1: add a new peer; we should share the TLK with them
646 // start with no trusted peers
647 [self.mockSOSAdapter.trustedPeers removeAllObjects];
649 [self startCKKSSubsystem];
650 [self performOctagonTLKUpload:self.ckksViews];
652 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
654 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
655 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
656 [self addGenericPassword: @"data" account: @"account-delete-me"];
657 OCMVerifyAllWithDelay(self.mockDatabase, 20);
660 self.aksLockState = true;
661 [self.lockStateTracker recheck];
663 // New peer arrives! This can't actually happen (since we have to be unlocked to accept a new peer), but this will exercise CKKS
664 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
665 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
667 // CKKS should notice that it has things to do...
668 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:20*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
671 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
672 self.aksLockState = false;
673 [self.lockStateTracker recheck];
675 OCMVerifyAllWithDelay(self.mockDatabase, 20);
677 // and return to ready
678 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
681 - (void)testAddItemDuringNewTLKSharesOnTrustSetAddition {
682 // step 1: add a new peer; we should share the TLK with them
683 // start with no trusted peers
684 [self.mockSOSAdapter.trustedPeers removeAllObjects];
686 [self startCKKSSubsystem];
687 [self performOctagonTLKUpload:self.ckksViews];
689 OCMVerifyAllWithDelay(self.mockDatabase, 20);
690 [self waitForCKModifications];
692 // Hold the TLK share modification
693 [self holdCloudKitModifications];
695 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
696 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
697 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
699 OCMVerifyAllWithDelay(self.mockDatabase, 20);
701 // While CloudKit is hanging the write, add an item
702 [self addGenericPassword: @"data" account: @"account-delete-me"];
704 // After that returns, release the write. CKKS should upload the new item
705 [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID
706 checkItem:[self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
707 [self releaseCloudKitModificationHold];
709 OCMVerifyAllWithDelay(self.mockDatabase, 20);
712 - (void)testSendNewTLKSharesOnTrustSetRemoval {
713 // Not implemented. Trust set removal demands a key roll, but let's not get ahead of ourselves...
716 - (void)testWaitForTLKWithMissingKeys {
717 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
718 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
720 // Test also starts with the TLK shared to all trusted peers from peer1
721 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
723 // self no longer has that key pair, but it does have a new one with the same peer ID....
724 self.mockSOSAdapter.selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:self.mockSOSAdapter.selfPeer.peerID
725 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
726 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
727 viewList:self.managedViewList];
728 self.pastSelfPeers = [NSMutableSet set];
730 // CKKS should become very upset, and enter waitfortlk.
731 [self startCKKSSubsystem];
732 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
733 OCMVerifyAllWithDelay(self.mockDatabase, 20);
736 - (void)testSendNewTLKShareToPeerOnPeerEncryptionKeyChange {
737 // If a peer changes its keys, CKKS should send it a new TLK share with the right keys
738 // This recovers from the remote peer losing its Octagon keys and making new ones
740 // step 1: add a new peer; we should share the TLK with them
741 // start with no trusted peers
742 [self startCKKSSubsystem];
743 [self performOctagonTLKUpload:self.ckksViews];
745 OCMVerifyAllWithDelay(self.mockDatabase, 20);
746 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
748 // Remote peer rolls its encryption key...
749 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
750 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
751 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
752 XCTAssertEqualObjects(share.share.receiverPeerID, self.remotePeer1.peerID, "Receiver peerID on TLKShare should match remote peer");
753 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer1.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer");
754 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
758 self.remotePeer1.encryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
759 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
761 OCMVerifyAllWithDelay(self.mockDatabase, 20);
762 [self waitForCKModifications];
764 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
767 - (void)testRecoverFromBrokenSignatureOnTLKShareDuetoSignatureKeyChange {
768 // If a peer changes its signature key, CKKS shouldn't necessarily enter 'error': it should enter 'waitfortlk'.
769 // The peer should then send us another TLKShare
770 // This recovers from the remote peer losing its Octagon keys and making new ones
772 // For this test, only have one peer
773 self.mockSOSAdapter.trustedPeers = [NSMutableSet setWithObject:self.remotePeer1];
775 // Test starts with nothing in database, but one in our fake CloudKit.
776 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
777 // Test also starts with the TLK shared to all trusted peers from remotePeer1
778 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
780 // BUT, remotePeer1 has rolled its signing key
781 self.remotePeer1.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
783 [self startCKKSSubsystem];
785 OCMVerifyAllWithDelay(self.mockDatabase, 20);
786 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
788 // Remote peer discovers its error and sends a new TLKShare! CKKS should recover and share itself a TLKShare
789 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
790 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
791 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
792 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match self peer");
793 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match self peer");
794 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
798 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
799 [self.keychainView notifyZoneChange:nil];
801 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
803 OCMVerifyAllWithDelay(self.mockDatabase, 20);
804 [self waitForCKModifications];
807 - (void)testSendNewTLKShareToSelfOnPeerSigningKeyChange {
808 // If a CKKS peer rolls its own keys, but has the TLK, it should write a new TLK share to itself with its new Octagon keys
809 // This recovers from the local peer losing its Octagon keys and making new ones
811 // For this test, only have one peer
812 self.mockSOSAdapter.trustedPeers = [NSMutableSet setWithObject:self.remotePeer1];
814 // Test starts with nothing in database, but one in our fake CloudKit.
815 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
816 // Test also starts with the TLK shared to all trusted peers from peer1
817 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
818 // The CKKS subsystem should accept the keys, and share the TLK back to itself
819 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
820 [self startCKKSSubsystem];
821 OCMVerifyAllWithDelay(self.mockDatabase, 20);
822 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
824 // Remote peer rolls its signing key, but hasn't updated its TLKShare. We should send it one.
825 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
826 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
827 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
828 XCTAssertEqualObjects(share.share.receiverPeerID, self.remotePeer1.peerID, "Receiver peerID on TLKShare should match remote peer");
829 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer1.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer");
830 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
834 self.remotePeer1.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
835 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
837 OCMVerifyAllWithDelay(self.mockDatabase, 20);
838 [self waitForCKModifications];
840 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
843 - (void)testSendNewTLKShareToPeerOnDisappearanceOfPeerKeys {
844 // If a CKKS peer deletes its own octagon keys (BUT WHY), local CKKS should be able to respond
846 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
847 // Test also starts with the TLK shared to all trusted peers from peer1
848 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
849 // The CKKS subsystem should accept the keys, and share the TLK back to itself
850 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
851 [self startCKKSSubsystem];
852 OCMVerifyAllWithDelay(self.mockDatabase, 20);
853 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
855 // Now, peer 1 updates its keys (to be nil). Local peer should re-send TLKShares to peer2.
857 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
858 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
859 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
860 XCTAssertEqualObjects(share.share.receiverPeerID, self.remotePeer2.peerID, "Receiver peerID on TLKShare should match remote peer");
861 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer2.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer");
862 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
866 CKKSSOSPeer* brokenRemotePeer1 = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.remotePeer1.peerID
867 encryptionPublicKey:nil
869 viewList:self.managedViewList];
870 [self.mockSOSAdapter.trustedPeers removeObject:self.remotePeer1];
871 [self.mockSOSAdapter.trustedPeers addObject:brokenRemotePeer1];
872 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
874 OCMVerifyAllWithDelay(self.mockDatabase, 20);
875 [self waitForCKModifications];
877 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
880 - (void)testSendNewTLKShareToPeerOnDisappearanceOfPeerSigningKey {
881 // If a CKKS peer rolls its own keys, but has the TLK, it should write a new TLK share to itself with its new Octagon keys
882 // This recovers from the local peer losing its Octagon keys and making new ones
884 // Test starts with nothing in database, but one in our fake CloudKit.
885 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
886 // Test also starts with the TLK shared to all trusted peers from peer1
887 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
888 // The CKKS subsystem should accept the keys, and share the TLK back to itself
889 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
890 [self startCKKSSubsystem];
891 OCMVerifyAllWithDelay(self.mockDatabase, 20);
892 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
894 // Now, peer 1 updates its signing key (to be nil). Local peer should re-send TLKShares to peer1 and peer2.
895 // Both should be sent because both peers don't have a signed TLKShare that gives them the TLK
897 XCTestExpectation *peer1Share = [self expectationWithDescription:@"share uploaded for peer1"];
898 XCTestExpectation *peer2Share = [self expectationWithDescription:@"share uploaded for peer2"];
900 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:2 zoneID:self.keychainZoneID
901 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
902 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
903 if([share.share.receiverPeerID isEqualToString:self.remotePeer1.peerID]) {
904 [peer1Share fulfill];
905 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer1.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer1");
907 if([share.share.receiverPeerID isEqualToString:self.remotePeer2.peerID]) {
908 [peer2Share fulfill];
909 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer2.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer2");
912 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
916 CKKSSOSPeer* brokenRemotePeer1 = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.remotePeer1.peerID
917 encryptionPublicKey:self.remotePeer1.publicEncryptionKey
919 viewList:self.managedViewList];
920 [self.mockSOSAdapter.trustedPeers removeObject:self.remotePeer1];
921 [self.mockSOSAdapter.trustedPeers addObject:brokenRemotePeer1];
922 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
924 OCMVerifyAllWithDelay(self.mockDatabase, 20);
925 [self waitForCKModifications];
926 [self waitForExpectations:@[peer1Share, peer2Share] timeout:5];
928 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
931 - (void)testSendNewTLKShareToSelfOnSelfKeyChanges {
932 // If a CKKS peer rolls its own keys, but has the TLK, it should write a new TLK share to itself with its new Octagon keys
933 // This recovers from the local peer losing its Octagon keys and making new ones
935 // Test starts with nothing in database, but one in our fake CloudKit.
936 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
937 // Test also starts with the TLK shared to all trusted peers from peer1
938 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
939 // The CKKS subsystem should accept the keys, and share the TLK back to itself
940 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
941 [self startCKKSSubsystem];
942 OCMVerifyAllWithDelay(self.mockDatabase, 20);
943 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
945 // Local peer rolls its encryption key (and loses the old ones)
946 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
947 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
948 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
949 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match current self");
950 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match current self");
951 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
952 NSError* signatureVerifyError = nil;
953 XCTAssertTrue([share verifySignature:share.signature verifyingPeer:self.mockSOSAdapter.selfPeer error:&signatureVerifyError], "New share's signature should verify");
954 XCTAssertNil(signatureVerifyError, "Should be no error verifying signature on new TLKShare");
958 self.mockSOSAdapter.selfPeer.encryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
959 self.pastSelfPeers = [NSMutableSet set];
960 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
962 OCMVerifyAllWithDelay(self.mockDatabase, 20);
963 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
965 // Now, local peer loses and rolls its signing key (and loses the old one)
966 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
967 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
968 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
969 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match current self");
970 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match current self");
971 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
972 NSError* signatureVerifyError = nil;
973 XCTAssertTrue([share verifySignature:share.signature verifyingPeer:self.mockSOSAdapter.selfPeer error:&signatureVerifyError], "New share's signature should verify");
974 XCTAssertNil(signatureVerifyError, "Should be no error verifying signature on new TLKShare");
978 self.mockSOSAdapter.selfPeer.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
979 self.pastSelfPeers = [NSMutableSet set];
980 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
982 OCMVerifyAllWithDelay(self.mockDatabase, 20);
983 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
986 - (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToRecentTLKShare {
987 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
989 // CKKS shouldn't reset this zone, due to a recent TLK Share from a trusted peer (indicating the presence of TLKs)
990 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 to:self.remotePeer1 zoneID:self.keychainZoneID];
992 NSDateComponents* offset = [[NSDateComponents alloc] init];
994 NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0];
995 for(CKRecord* record in self.keychainZone.currentDatabase.allValues) {
996 if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) {
997 record.creationDate = updateTime;
998 record.modificationDate = updateTime;
1002 self.keychainZone.flag = true;
1003 [self startCKKSSubsystem];
1005 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], @"Key state should become 'waitfortlk'");
1007 XCTAssertTrue(self.keychainZone.flag, "Zone flag should not have been reset to false");
1010 - (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToVeryRecentUntrustedTLKShare {
1011 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1013 // CKKS shouldn't reset this zone, due to a very recent (but untrusted) TLK Share. You can hit this getting a circle reset; the device with the TLKs will have a CFU.
1014 CKKSSOSSelfPeer* untrustedPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"untrusted-peer"
1015 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1016 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1017 viewList:self.managedViewList];
1018 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:untrustedPeer to:untrustedPeer zoneID:self.keychainZoneID];
1020 NSDateComponents* offset = [[NSDateComponents alloc] init];
1022 NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0];
1023 for(CKRecord* record in self.keychainZone.currentDatabase.allValues) {
1024 if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) {
1025 record.creationDate = updateTime;
1026 record.modificationDate = updateTime;
1030 self.keychainZone.flag = true;
1031 [self startCKKSSubsystem];
1033 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], @"Key state should become 'waitfortlk'");
1034 XCTAssertTrue(self.keychainZone.flag, "Zone flag should not have been reset to false");
1036 // And ensure it doesn't go on to 'reset'
1037 XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateResettingZone] wait:100*NSEC_PER_MSEC], @"Key state should not become 'resetzone'");
1040 - (void)testResetCloudKitZoneFromWaitForTLKDueToUntustedTLKShareNotRecentEnough {
1041 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1043 // CKKS shouldn't reset this zone, due to a recent TLK Share (indicating the presence of TLKs)
1044 CKKSSOSSelfPeer* untrustedPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"untrusted-peer"
1045 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1046 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1047 viewList:self.managedViewList];
1048 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:untrustedPeer to:untrustedPeer zoneID:self.keychainZoneID];
1050 NSDateComponents* offset = [[NSDateComponents alloc] init];
1052 NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0];
1053 for(CKRecord* record in self.keychainZone.currentDatabase.allValues) {
1054 if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) {
1055 record.creationDate = updateTime;
1056 record.modificationDate = updateTime;
1060 self.silentZoneDeletesAllowed = true;
1061 self.keychainZone.flag = true;
1062 [self startCKKSSubsystem];
1063 [self performOctagonTLKUpload:self.ckksViews];
1065 // Then we should reset.
1066 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1067 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
1069 // And the zone should have been cleared and re-made
1070 XCTAssertFalse(self.keychainZone.flag, "Zone flag should have been reset to false");
1073 - (void)testNoSOSSelfEncryptionKeys {
1074 // If you lose your local encryption keys, CKKS should do something reasonable
1076 // Test also starts with the TLK shared to all trusted peers from peer1
1077 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1078 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1079 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1081 // But, we lost our local keys :(
1082 CKKSSOSSelfPeer* oldSelfPeer = self.mockSOSAdapter.selfPeer;
1084 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecParam description:@"injected test failure"];
1086 // CKKS subsystem should realize that it can't read the shares it has, and enter waitfortlk
1087 [self startCKKSSubsystem];
1088 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortlk'");
1090 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1091 [self waitForCKModifications];
1093 // Fetching status should be quick
1094 XCTestExpectation* callbackOccurs = [self expectationWithDescription:@"callback-occurs"];
1095 [self.ckksControl rpcStatus:@"keychain" reply:^(NSArray<NSDictionary*>* result, NSError* error) {
1096 XCTAssertNil(error, "should be no error fetching status for keychain");
1097 [callbackOccurs fulfill];
1099 [self waitForExpectations:@[callbackOccurs] timeout:20];
1101 // But, if by some miracle those keys come back, CKKS should be able to recover
1102 // It'll also upload itself a TLK share
1103 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1105 self.mockSOSAdapter.selfPeer = oldSelfPeer;
1106 self.mockSOSAdapter.selfPeerError = nil;
1108 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
1109 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready''");
1111 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1112 [self waitForCKModifications];
1115 - (void)testNoSOSSelfEncryptionKeysDuringCreation {
1116 // If you lose your local encryption keys, CKKS should do something reasonable
1118 // But, things don't exist :(
1119 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecParam description:@"injected test failure"];
1121 [self startCKKSSubsystem];
1123 NSMutableArray<CKKSResultOperation<CKKSKeySetProviderOperationProtocol>*>* keysetOps = [NSMutableArray array];
1124 for(CKKSKeychainView* view in self.ckksViews) {
1125 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLKCreation] wait:40*NSEC_PER_SEC], @"key state should enter 'waitfortlkcreation' (view %@)", view);
1126 [keysetOps addObject: [view findKeySet]];
1129 // Now that we've kicked them all off, wait for them to not crash
1130 for(CKKSKeychainView* view in self.ckksViews) {
1131 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateError] wait:40*NSEC_PER_SEC], @"key state should enter 'error'");
1135 // Fetching status should be quick
1136 XCTestExpectation* callbackOccurs = [self expectationWithDescription:@"callback-occurs"];
1137 [self.ckksControl rpcStatus:@"keychain" reply:^(NSArray<NSDictionary*>* result, NSError* error) {
1138 XCTAssertNil(error, "should be no error fetching status for keychain");
1139 [callbackOccurs fulfill];
1141 [self waitForExpectations:@[callbackOccurs] timeout:20];
1143 // But, if by some miracle those keys come back, CKKS should be able to recover
1144 // It'll also upload itself a TLK share
1145 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1147 self.mockSOSAdapter.selfPeer = oldSelfPeer;
1148 self.mockSOSAdapter.selfPeerError = nil;
1150 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
1151 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready''");
1153 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1154 [self waitForCKModifications];*/
1158 - (void)testNoOctagonSelfEncryptionKeys {
1159 // If you lose your local encryption keys, CKKS should do something reasonable
1161 // Test also starts with the TLK shared to all trusted peers from peer1
1162 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1163 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1164 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1167 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1168 [self startCKKSSubsystem];
1169 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1171 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1172 [self waitForCKModifications];
1174 // But, we lose our local keys :(
1175 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:TrustedPeersHelperErrorDomain
1177 description:@"injected test failure (Octagon key loss)"];
1179 // Kick off TLK checking
1180 [self.keychainView trustedPeerSetChanged:self.mockSOSAdapter];
1181 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'untrusted'");
1183 // Fetching status should be quick
1184 XCTestExpectation* callbackOccurs = [self expectationWithDescription:@"callback-occurs"];
1185 [self.ckksControl rpcStatus:@"keychain" reply:^(NSArray<NSDictionary*>* result, NSError* error) {
1186 XCTAssertNil(error, "should be no error fetching status for keychain");
1187 [callbackOccurs fulfill];
1189 [self waitForExpectations:@[callbackOccurs] timeout:20];
1191 // and it should pause, and not spin infinitely
1192 XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateInitialized] wait:2*NSEC_PER_SEC], "Key state should not become 'initialized'");
1195 - (void)testLoseTrustIfSelfPeerNotTrusted {
1196 // If you decide you don't trust yourself, CKKS should do something reasonable
1198 // Test also starts with the TLK shared to all trusted peers from peer1
1199 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1200 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1201 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1203 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1204 [self startCKKSSubsystem];
1205 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1207 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1208 [self waitForCKModifications];
1210 self.mockSOSAdapter.excludeSelfPeerFromTrustSet = true;
1212 // Kick off TLK checking
1213 [self.keychainView trustedPeerSetChanged:self.mockSOSAdapter];
1214 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'untrusted'");
1216 // and it should pause, and not spin infinitely
1217 XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateInitialized] wait:2*NSEC_PER_SEC], "Key state should not become 'initialized'");
1220 - (void)testDontLoseTrustIfNonessentialTrustStateIsBroken {
1221 // if a non-essential but broken peer provider exists, CKKS shouldn't pay it any mind
1223 // Test also starts with the TLK shared to all trusted peers from peer1
1224 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1225 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1226 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1229 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1230 [self startCKKSSubsystem];
1231 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1233 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1234 [self waitForCKModifications];
1236 // To fake this, make another mock SOS adapter, but make it nonessential
1237 CKKSSOSSelfPeer* brokenSelfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"local-peer"
1238 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1239 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1240 viewList:self.managedViewList];
1241 CKKSMockSOSPresentAdapter* brokenAdapter = [[CKKSMockSOSPresentAdapter alloc] initWithSelfPeer:brokenSelfPeer
1242 trustedPeers:[NSSet set]
1244 brokenAdapter.excludeSelfPeerFromTrustSet = true;
1246 [self.keychainView endTrustedOperation];
1247 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");
1249 [self.keychainView beginTrustedOperation:@[self.mockSOSAdapter, brokenAdapter] suggestTLKUpload:self.suggestTLKUpload];
1251 // CKKS should ignore the non-essential and broken peer adapter
1252 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1255 - (void)testTLKSharesForOctagonPeer {
1256 // Launch first for SOS only
1257 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1258 [self saveTLKMaterialToKeychain:self.keychainZoneID];
1260 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
1261 [self startCKKSSubsystem];
1263 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
1264 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1265 [self waitForCKModifications];
1267 [self.keychainView endTrustedOperation];
1268 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");
1270 // Spin up Octagon. We expect an upload of 3 TLK shares, this time for the octagon peer
1271 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1272 [self.keychainView beginTrustedOperation:@[self.mockSOSAdapter, self.mockOctagonAdapter] suggestTLKUpload:self.suggestTLKUpload];
1273 [self.mockOctagonAdapter sendTrustedPeerSetChangedUpdate];
1275 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1276 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");