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 dispatchSyncWithReadOnlySQLTransaction:^{
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");
153 /* As part of 48178481, we disabled CKKS 'past selves' for SOS */
154 - (void)DISABLEDtestAcceptExistingTLKSharedKeyHierarchyForPastSelf {
155 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
156 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
158 // Test also starts with the TLK shared to all trusted peers from peer1
159 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
161 // Self rolls its keys and ID...
162 [self.pastSelfPeers addObject:self.mockSOSAdapter.selfPeer];
163 self.mockSOSAdapter.selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"new-local-peer"
164 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
165 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
166 viewList:self.managedViewList];
168 // The CKKS subsystem should accept the keys, and share the TLK back to itself
169 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
170 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
171 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
172 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match current self");
173 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match current self");
174 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
177 [self startCKKSSubsystem];
178 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
180 OCMVerifyAllWithDelay(self.mockDatabase, 20);
182 // Verify that there are three local keys, and three local current key records
183 __weak __typeof(self) weakSelf = self;
184 [self.keychainView dispatchSyncWithReadOnlySQLTransaction:^{
185 __strong __typeof(weakSelf) strongSelf = weakSelf;
186 XCTAssertNotNil(strongSelf, "self exists");
188 NSError* error = nil;
190 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.keychainZoneID error:&error];
191 XCTAssertNil(error, "no error fetching keys");
192 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
194 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:&error];
195 XCTAssertNil(error, "no error fetching current keys");
196 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
200 - (void)testAcceptExistingTLKSharedKeyHierarchyAndUse {
201 // Test starts with nothing in database, but one in our fake CloudKit.
202 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
204 // Test also starts with the TLK shared to all trusted peers from peer1
205 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
207 // The CKKS subsystem should accept the keys, and share the TLK back to itself
208 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
209 [self startCKKSSubsystem];
210 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
212 // We expect a single record to be uploaded for each key class
213 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
214 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
215 [self addGenericPassword: @"data" account: @"account-delete-me"];
216 OCMVerifyAllWithDelay(self.mockDatabase, 20);
218 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
219 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
220 [self addGenericPassword:@"asdf"
221 account:@"account-class-A"
223 access:(id)kSecAttrAccessibleWhenUnlocked
224 expecting:errSecSuccess
225 message:@"Adding class A item"];
226 OCMVerifyAllWithDelay(self.mockDatabase, 20);
229 - (void)testNewTLKSharesHaveChangeTags {
230 // Since there's currently no flow for CKKS to ever update a TLK share when things are working properly, do some hackery
232 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
233 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
235 // Test also starts with the TLK shared to all trusted peers from peer1
236 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
237 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
239 // The CKKS subsystem should accept the keys, and share the TLK back to itself
240 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
241 [self startCKKSSubsystem];
242 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
244 OCMVerifyAllWithDelay(self.mockDatabase, 20);
245 [self waitForCKModifications];
247 // Verify that making a new share will have the old share's change tag
248 __weak __typeof(self) weakSelf = self;
249 [self.keychainView dispatchSyncWithReadOnlySQLTransaction:^{
250 __strong __typeof(weakSelf) strongSelf = weakSelf;
251 XCTAssertNotNil(strongSelf, "self exists");
253 NSError* error = nil;
254 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:strongSelf.keychainZoneKeys.tlk
255 as:strongSelf.mockSOSAdapter.selfPeer
256 to:strongSelf.mockSOSAdapter.selfPeer
260 XCTAssertNil(error, "Shouldn't be an error creating a share");
261 XCTAssertNotNil(share, "Should be able to create share");
263 CKRecord* newRecord = [share CKRecordWithZoneID:strongSelf.keychainZoneID];
264 XCTAssertNotNil(newRecord, "Should be able to create a CKRecord");
266 CKRecord* cloudKitRecord = strongSelf.keychainZone.currentDatabase[newRecord.recordID];
267 XCTAssertNotNil(cloudKitRecord, "Should have found existing CKRecord in cloudkit");
268 XCTAssertNotNil(cloudKitRecord.recordChangeTag, "Existing record should have a change tag");
270 XCTAssertEqualObjects(cloudKitRecord.recordChangeTag, newRecord.recordChangeTag, "Change tags on existing and new records should match");
274 - (void)testReceiveTLKShareRecordsAndDeletes {
275 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
276 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
278 // Test also starts with the TLK shared to all trusted peers from peer1
279 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
281 // The CKKS subsystem should accept the keys, and share the TLK back to itself
282 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
283 [self startCKKSSubsystem];
284 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
286 // The CKKS subsystem should not try to write anything to the CloudKit database while it's accepting the keys
287 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
288 OCMVerifyAllWithDelay(self.mockDatabase, 20);
291 // Make another share, but from an untrusted peer to some other peer. local shouldn't necessarily care.
292 NSError* error = nil;
293 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:self.keychainZoneKeys.tlk
294 as:self.untrustedPeer
299 XCTAssertNil(error, "Should have been no error sharing a CKKSKey");
300 XCTAssertNotNil(share, "Should be able to create a share");
302 CKRecord* shareCKRecord = [share CKRecordWithZoneID: self.keychainZoneID];
303 XCTAssertNotNil(shareCKRecord, "Should have been able to create a CKRecord");
304 [self.keychainZone addToZone:shareCKRecord];
305 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
306 [self.keychainView waitForFetchAndIncomingQueueProcessing];
308 [self.keychainView dispatchSyncWithReadOnlySQLTransaction:^{
309 NSError* blockerror = nil;
310 CKKSTLKShareRecord* localshare = [CKKSTLKShareRecord tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
311 XCTAssertNil(blockerror, "Shouldn't error finding TLKShare record in database");
312 XCTAssertNotNil(localshare, "Should be able to find a TLKShare record in database");
315 // Delete the record in CloudKit...
316 [self.keychainZone deleteCKRecordIDFromZone:shareCKRecord.recordID];
317 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
318 [self.keychainView waitForFetchAndIncomingQueueProcessing];
320 // Should be gone now.
321 [self.keychainView dispatchSyncWithReadOnlySQLTransaction:^{
322 NSError* blockerror = nil;
323 CKKSTLKShareRecord* localshare = [CKKSTLKShareRecord tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
325 XCTAssertNil(blockerror, "Shouldn't error trying to find non-existent TLKShare record in database");
326 XCTAssertNil(localshare, "Shouldn't be able to find a TLKShare record in database");
330 - (void)testReceiveSharedTLKWhileInWaitForTLK {
331 // Test starts with nothing in database, but one in our fake CloudKit.
332 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
333 [self putFakeDeviceStatusInCloudKit:self.keychainZoneID];
335 // Spin up CKKS subsystem.
336 [self startCKKSSubsystem];
338 // The CKKS subsystem should not try to write anything to the CloudKit database, but it should enter waitfortlk
339 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
341 // peer1 arrives to save the day
342 // The CKKS subsystem should accept the keys, and share the TLK back to itself
343 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
345 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
346 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
347 [self.keychainView waitForFetchAndIncomingQueueProcessing];
349 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
351 // We expect a single record to be uploaded for each key class
352 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
353 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
354 [self addGenericPassword: @"data" account: @"account-delete-me"];
355 OCMVerifyAllWithDelay(self.mockDatabase, 20);
357 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
358 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
359 [self addGenericPassword:@"asdf"
360 account:@"account-class-A"
362 access:(id)kSecAttrAccessibleWhenUnlocked
363 expecting:errSecSuccess
364 message:@"Adding class A item"];
365 OCMVerifyAllWithDelay(self.mockDatabase, 20);
368 - (void)testReceiveTLKShareWhileLocked {
369 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
370 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
372 // Test also starts with the TLK shared to all trusted peers from peer1
373 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
375 self.aksLockState = true;
376 [self.lockStateTracker recheck];
378 // Spin up CKKS subsystem.
379 [self startCKKSSubsystem];
381 // The CKKS subsystem should not try to write anything to the CloudKit database, but it should enter waitforunlock
382 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForUnlock] wait:20*NSEC_PER_SEC], "Key state should become waitforunlock");
384 // Now unlock things. We expect a TLKShare upload.
385 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
387 self.aksLockState = false;
388 [self.lockStateTracker recheck];
390 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
391 OCMVerifyAllWithDelay(self.mockDatabase, 20);
394 - (void)testUploadTLKSharesForExistingHierarchy {
395 // Test starts with key material locally and in CloudKit.
396 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
397 [self saveTLKMaterialToKeychain:self.keychainZoneID];
399 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
400 [self startCKKSSubsystem];
402 OCMVerifyAllWithDelay(self.mockDatabase, 20);
405 - (void)testUploadTLKSharesForExistingHierarchyOnRestart {
406 // Bring up CKKS. It'll upload a few TLK Shares, but we'll delete them to get into state
407 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
408 [self saveTLKMaterialToKeychain:self.keychainZoneID];
410 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
411 [self startCKKSSubsystem];
413 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
414 OCMVerifyAllWithDelay(self.mockDatabase, 20);
415 [self waitForCKModifications];
417 // Now, delete all the TLK Shares, so CKKS will upload them again
418 [self.keychainView dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
419 NSError* error = nil;
420 [CKKSTLKShareRecord deleteAll:self.keychainZoneID error:&error];
421 XCTAssertNil(error, "Shouldn't be an error deleting all TLKShares");
423 NSArray<CKRecord*>* records = [self.zones[self.keychainZoneID].currentDatabase allValues];
424 for(CKRecord* record in records) {
425 if([record.recordType isEqualToString:SecCKRecordTLKShareType]) {
426 [self.zones[self.keychainZoneID] deleteFromHistory:record.recordID];
430 return CKKSDatabaseTransactionCommit;
433 // Restart. We expect an upload of 3 TLK shares.
434 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
435 self.keychainView = [self.injectedManager restartZone: self.keychainZoneID.zoneName];
436 [self beginSOSTrustedViewOperation:self.keychainView];
438 OCMVerifyAllWithDelay(self.mockDatabase, 20);
439 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
442 - (void)testHandleExternalSharedTLKRoll {
443 // Test starts with key material locally and in CloudKit.
444 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
445 [self saveTLKMaterialToKeychain:self.keychainZoneID];
447 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
448 [self startCKKSSubsystem];
450 OCMVerifyAllWithDelay(self.mockDatabase, 20);
451 [self waitForCKModifications];
453 // Now the external peer rolls the TLK and updates the shares
454 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
455 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
457 // CKKS will share the TLK back to itself
458 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
460 // Trigger a notification
461 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
462 [self.keychainView waitForFetchAndIncomingQueueProcessing];
463 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
464 OCMVerifyAllWithDelay(self.mockDatabase, 20);
465 [self waitForCKModifications];
467 // We expect a single record to be uploaded.
468 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
469 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
470 [self addGenericPassword: @"data" account: @"account-delete-me-rolled-key"];
472 OCMVerifyAllWithDelay(self.mockDatabase, 20);
475 - (void)testUploadTLKSharesForExternalTLKRollWithoutShares {
476 // Test starts with key material locally and in CloudKit.
477 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
478 [self saveTLKMaterialToKeychain:self.keychainZoneID];
480 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
481 [self startCKKSSubsystem];
483 OCMVerifyAllWithDelay(self.mockDatabase, 20);
485 // Now, an old (Tigris) peer rolls the TLK, but doesn't share it
486 // CKKS should get excited and throw 3 new share records up
487 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
489 // Wait for that modification to finish before changing CK data
490 [self waitForCKModifications];
492 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
493 [self saveTLKMaterialToKeychain:self.keychainZoneID];
495 // Trigger a notification
496 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
498 OCMVerifyAllWithDelay(self.mockDatabase, 20);
499 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
501 // We expect a single record to be uploaded.
502 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
503 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
504 [self addGenericPassword: @"data" account: @"account-delete-me-rolled-key"];
506 OCMVerifyAllWithDelay(self.mockDatabase, 20);
509 - (void)testRecoverFromTLKShareUploadFailure {
510 // Test starts with key material locally and in CloudKit.
511 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
512 [self saveTLKMaterialToKeychain:self.keychainZoneID];
514 __weak __typeof(self) weakSelf = self;
515 [self failNextCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID blockAfterReject:^{
516 __strong __typeof(self) strongSelf = weakSelf;
517 [strongSelf expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
519 [self startCKKSSubsystem];
521 OCMVerifyAllWithDelay(self.mockDatabase, 20);
522 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
525 - (void)testFillInMissingPeerShares {
526 // Test starts with nothing in database, but one in our fake CloudKit.
527 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
529 // Test also starts with the TLK shared to just the local peer from peer1
530 // We expect the local peer to send it to peer2
531 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
533 // The CKKS subsystem should accept the keys, and share the TLK back to itself
534 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
535 [self startCKKSSubsystem];
536 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
538 // We expect a single record to be uploaded for each key class
539 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
540 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
541 [self addGenericPassword: @"data" account: @"account-delete-me"];
542 OCMVerifyAllWithDelay(self.mockDatabase, 20);
544 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
545 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
546 [self addGenericPassword:@"asdf"
547 account:@"account-class-A"
549 access:(id)kSecAttrAccessibleWhenUnlocked
550 expecting:errSecSuccess
551 message:@"Adding class A item"];
552 OCMVerifyAllWithDelay(self.mockDatabase, 20);
555 - (void)testDontAcceptTLKFromUntrustedPeer {
556 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
557 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
558 // The remote peer should also have given the TLK to a non-TLKShare peer (which is also offline)
559 [self putFakeDeviceStatusInCloudKit:self.keychainZoneID];
561 // Test also starts with the key hierarchy shared from a non-trusted peer
562 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer zoneID:self.keychainZoneID];
564 // The CKKS subsystem should go into waitfortlk, since it doesn't trust this peer, but the peer is active
565 [self startCKKSSubsystem];
566 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
569 - (void)testAcceptSharedTLKOnTrustSetAdditionOfSharer {
570 // Test starts with nothing in database, but key hierarchy in our fake CloudKit.
571 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
572 [self putFakeDeviceStatusInCloudKit:self.keychainZoneID];
574 // Test also starts with the key hierarchy shared from a non-trusted peer
575 // note that it would share it itself too
576 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer zoneID:self.keychainZoneID];
577 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.untrustedPeer to:self.untrustedPeer zoneID:self.keychainZoneID];
579 // The CKKS subsystem should go into waitfortlk, since it doesn't trust this peer, but the peer is active
580 [self startCKKSSubsystem];
581 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:2000*NSEC_PER_SEC], "Key state should become waitfortlk");
583 // Wait to be sure we really get into that state
584 [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
586 // Now, trust the previously-untrusted peer
587 [self.mockSOSAdapter.trustedPeers addObject: self.untrustedPeer];
588 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
590 // The CKKS subsystem should now accept the key, and share the TLK back to itself
591 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
593 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
595 // And use it as well
596 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
597 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
598 [self addGenericPassword: @"data" account: @"account-delete-me"];
599 OCMVerifyAllWithDelay(self.mockDatabase, 20);
602 - (void)testSendNewTLKSharesOnTrustSetAddition {
603 // step 1: add a new peer; we should share the TLK with them
604 // start with no trusted peers
605 [self.mockSOSAdapter.trustedPeers removeAllObjects];
607 [self startCKKSSubsystem];
608 [self performOctagonTLKUpload:self.ckksViews];
610 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
611 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
612 [self addGenericPassword: @"data" account: @"account-delete-me"];
613 OCMVerifyAllWithDelay(self.mockDatabase, 20);
615 // Cool! New peer arrives!
616 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
617 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
618 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
620 OCMVerifyAllWithDelay(self.mockDatabase, 20);
621 [self waitForCKModifications];
623 // step 2: add a new peer who already has a share; no share should be created
624 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 to:self.remotePeer2 zoneID:self.keychainZoneID];
625 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
626 [self.keychainView waitForFetchAndIncomingQueueProcessing];
628 // CKKS should not upload a tlk share for this peer
629 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer2];
630 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
632 [self.keychainView waitUntilAllOperationsAreFinished];
635 - (void)testFillInMissingPeerSharesAfterUnlock {
636 // step 1: add a new peer; we should share the TLK with them
637 // start with no trusted peers
638 [self.mockSOSAdapter.trustedPeers removeAllObjects];
640 [self startCKKSSubsystem];
641 [self performOctagonTLKUpload:self.ckksViews];
643 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
645 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
646 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
647 [self addGenericPassword: @"data" account: @"account-delete-me"];
648 OCMVerifyAllWithDelay(self.mockDatabase, 20);
651 self.aksLockState = true;
652 [self.lockStateTracker recheck];
654 // New peer arrives! This can't actually happen (since we have to be unlocked to accept a new peer), but this will exercise CKKS
655 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
656 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
658 // CKKS should notice that it has things to do...
659 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:20*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
662 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
663 self.aksLockState = false;
664 [self.lockStateTracker recheck];
666 OCMVerifyAllWithDelay(self.mockDatabase, 20);
668 // and return to ready
669 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
672 - (void)testAddItemDuringNewTLKSharesOnTrustSetAddition {
673 // step 1: add a new peer; we should share the TLK with them
674 // start with no trusted peers
675 [self.mockSOSAdapter.trustedPeers removeAllObjects];
677 [self startCKKSSubsystem];
678 [self performOctagonTLKUpload:self.ckksViews];
680 OCMVerifyAllWithDelay(self.mockDatabase, 20);
681 [self waitForCKModifications];
683 // Hold the TLK share modification
684 [self holdCloudKitModifications];
686 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
687 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
688 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
690 OCMVerifyAllWithDelay(self.mockDatabase, 20);
692 // While CloudKit is hanging the write, add an item
693 [self addGenericPassword: @"data" account: @"account-delete-me"];
695 // After that returns, release the write. CKKS should upload the new item
696 [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID
697 checkItem:[self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
698 [self releaseCloudKitModificationHold];
700 OCMVerifyAllWithDelay(self.mockDatabase, 20);
703 - (void)testSendNewTLKSharesOnTrustSetRemoval {
704 // Not implemented. Trust set removal demands a key roll, but let's not get ahead of ourselves...
707 - (void)testWaitForTLKWithMissingKeys {
708 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
709 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
711 // Test also starts with the TLK shared to all trusted peers from peer1
712 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
714 // self no longer has that key pair, but it does have a new one with the same peer ID....
715 self.mockSOSAdapter.selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:self.mockSOSAdapter.selfPeer.peerID
716 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
717 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
718 viewList:self.managedViewList];
719 self.pastSelfPeers = [NSMutableSet set];
721 // CKKS should become very upset, and enter waitfortlk.
722 [self startCKKSSubsystem];
723 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
724 OCMVerifyAllWithDelay(self.mockDatabase, 20);
727 - (void)testSendNewTLKShareToPeerOnPeerEncryptionKeyChange {
728 // If a peer changes its keys, CKKS should send it a new TLK share with the right keys
729 // This recovers from the remote peer losing its Octagon keys and making new ones
731 // step 1: add a new peer; we should share the TLK with them
732 // start with no trusted peers
733 [self startCKKSSubsystem];
734 [self performOctagonTLKUpload:self.ckksViews];
736 OCMVerifyAllWithDelay(self.mockDatabase, 20);
737 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
739 // Remote peer rolls its encryption key...
740 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
741 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
742 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
743 XCTAssertEqualObjects(share.share.receiverPeerID, self.remotePeer1.peerID, "Receiver peerID on TLKShare should match remote peer");
744 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer1.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer");
745 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
749 self.remotePeer1.encryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
750 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
752 OCMVerifyAllWithDelay(self.mockDatabase, 20);
753 [self waitForCKModifications];
755 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
758 - (void)testRecoverFromBrokenSignatureOnTLKShareDuetoSignatureKeyChange {
759 // If a peer changes its signature key, CKKS shouldn't necessarily enter 'error': it should enter 'waitfortlk'.
760 // The peer should then send us another TLKShare
761 // This recovers from the remote peer losing its Octagon keys and making new ones
763 // For this test, only have one peer
764 self.mockSOSAdapter.trustedPeers = [NSMutableSet setWithObject:self.remotePeer1];
766 // Test starts with nothing in database, but one in our fake CloudKit.
767 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
768 // Test also starts with the TLK shared to all trusted peers from remotePeer1
769 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
771 // BUT, remotePeer1 has rolled its signing key
772 self.remotePeer1.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
774 [self startCKKSSubsystem];
776 OCMVerifyAllWithDelay(self.mockDatabase, 20);
777 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
779 // Remote peer discovers its error and sends a new TLKShare! CKKS should recover and share itself a TLKShare
780 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
781 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
782 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
783 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match self peer");
784 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match self peer");
785 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
789 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
790 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
792 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
794 OCMVerifyAllWithDelay(self.mockDatabase, 20);
795 [self waitForCKModifications];
798 - (void)testSendNewTLKShareToSelfOnPeerSigningKeyChange {
799 // 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
800 // This recovers from the local peer losing its Octagon keys and making new ones
802 // For this test, only have one peer
803 self.mockSOSAdapter.trustedPeers = [NSMutableSet setWithObject:self.remotePeer1];
805 // Test starts with nothing in database, but one in our fake CloudKit.
806 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
807 // Test also starts with the TLK shared to all trusted peers from peer1
808 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
809 // The CKKS subsystem should accept the keys, and share the TLK back to itself
810 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
811 [self startCKKSSubsystem];
812 OCMVerifyAllWithDelay(self.mockDatabase, 20);
813 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
815 // Remote peer rolls its signing key, but hasn't updated its TLKShare. We should send it one.
816 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
817 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
818 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
819 XCTAssertEqualObjects(share.share.receiverPeerID, self.remotePeer1.peerID, "Receiver peerID on TLKShare should match remote peer");
820 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer1.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer");
821 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
825 self.remotePeer1.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
826 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
828 OCMVerifyAllWithDelay(self.mockDatabase, 20);
829 [self waitForCKModifications];
831 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
834 - (void)testSendNewTLKShareToPeerOnDisappearanceOfPeerKeys {
835 // If a CKKS peer deletes its own octagon keys (BUT WHY), local CKKS should be able to respond
837 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
838 // Test also starts with the TLK shared to all trusted peers from peer1
839 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
840 // The CKKS subsystem should accept the keys, and share the TLK back to itself
841 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
842 [self startCKKSSubsystem];
843 OCMVerifyAllWithDelay(self.mockDatabase, 20);
844 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
846 // Now, peer 1 updates its keys (to be nil). Local peer should re-send TLKShares to peer2.
848 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
849 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
850 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
851 XCTAssertEqualObjects(share.share.receiverPeerID, self.remotePeer2.peerID, "Receiver peerID on TLKShare should match remote peer");
852 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer2.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer");
853 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
857 CKKSSOSPeer* brokenRemotePeer1 = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.remotePeer1.peerID
858 encryptionPublicKey:nil
860 viewList:self.managedViewList];
861 [self.mockSOSAdapter.trustedPeers removeObject:self.remotePeer1];
862 [self.mockSOSAdapter.trustedPeers addObject:brokenRemotePeer1];
863 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
865 OCMVerifyAllWithDelay(self.mockDatabase, 20);
866 [self waitForCKModifications];
868 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
871 - (void)testSendNewTLKShareToPeerOnDisappearanceOfPeerSigningKey {
872 // 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
873 // This recovers from the local peer losing its Octagon keys and making new ones
875 // Test starts with nothing in database, but one in our fake CloudKit.
876 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
877 // Test also starts with the TLK shared to all trusted peers from peer1
878 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
879 // The CKKS subsystem should accept the keys, and share the TLK back to itself
880 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
881 [self startCKKSSubsystem];
882 OCMVerifyAllWithDelay(self.mockDatabase, 20);
883 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
885 // Now, peer 1 updates its signing key (to be nil). Local peer should re-send TLKShares to peer1 and peer2.
886 // Both should be sent because both peers don't have a signed TLKShare that gives them the TLK
888 XCTestExpectation *peer1Share = [self expectationWithDescription:@"share uploaded for peer1"];
889 XCTestExpectation *peer2Share = [self expectationWithDescription:@"share uploaded for peer2"];
891 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:2 zoneID:self.keychainZoneID
892 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
893 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
894 if([share.share.receiverPeerID isEqualToString:self.remotePeer1.peerID]) {
895 [peer1Share fulfill];
896 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer1.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer1");
898 if([share.share.receiverPeerID isEqualToString:self.remotePeer2.peerID]) {
899 [peer2Share fulfill];
900 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.remotePeer2.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match remote peer2");
903 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
907 CKKSSOSPeer* brokenRemotePeer1 = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.remotePeer1.peerID
908 encryptionPublicKey:self.remotePeer1.publicEncryptionKey
910 viewList:self.managedViewList];
911 [self.mockSOSAdapter.trustedPeers removeObject:self.remotePeer1];
912 [self.mockSOSAdapter.trustedPeers addObject:brokenRemotePeer1];
913 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
915 OCMVerifyAllWithDelay(self.mockDatabase, 20);
916 [self waitForCKModifications];
917 [self waitForExpectations:@[peer1Share, peer2Share] timeout:5];
919 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
922 - (void)testSendNewTLKShareToSelfOnSelfKeyChanges {
923 // 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
924 // This recovers from the local peer losing its Octagon keys and making new ones
926 // Test starts with nothing in database, but one in our fake CloudKit.
927 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
928 // Test also starts with the TLK shared to all trusted peers from peer1
929 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
930 // The CKKS subsystem should accept the keys, and share the TLK back to itself
931 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
932 [self startCKKSSubsystem];
933 OCMVerifyAllWithDelay(self.mockDatabase, 20);
934 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
936 // Local peer rolls its encryption key (and loses the old ones)
937 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
938 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
939 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
940 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match current self");
941 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match current self");
942 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
943 NSError* signatureVerifyError = nil;
944 XCTAssertTrue([share verifySignature:share.signature verifyingPeer:self.mockSOSAdapter.selfPeer error:&signatureVerifyError], "New share's signature should verify");
945 XCTAssertNil(signatureVerifyError, "Should be no error verifying signature on new TLKShare");
949 self.mockSOSAdapter.selfPeer.encryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
950 self.pastSelfPeers = [NSMutableSet set];
951 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
953 OCMVerifyAllWithDelay(self.mockDatabase, 20);
954 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
956 // Now, local peer loses and rolls its signing key (and loses the old one)
957 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID
958 checkModifiedRecord:^BOOL(CKRecord* _Nonnull record) {
959 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
960 XCTAssertEqualObjects(share.share.receiverPeerID, self.mockSOSAdapter.selfPeer.peerID, "Receiver peerID on TLKShare should match current self");
961 XCTAssertEqualObjects(share.share.receiverPublicEncryptionKeySPKI, self.mockSOSAdapter.selfPeer.publicEncryptionKey.keyData, "Receiver encryption key on TLKShare should match current self");
962 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
963 NSError* signatureVerifyError = nil;
964 XCTAssertTrue([share verifySignature:share.signature verifyingPeer:self.mockSOSAdapter.selfPeer error:&signatureVerifyError], "New share's signature should verify");
965 XCTAssertNil(signatureVerifyError, "Should be no error verifying signature on new TLKShare");
969 self.mockSOSAdapter.selfPeer.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
970 self.pastSelfPeers = [NSMutableSet set];
971 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
973 OCMVerifyAllWithDelay(self.mockDatabase, 20);
974 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
977 - (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToRecentTLKShare {
978 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
980 // CKKS shouldn't reset this zone, due to a recent TLK Share from a trusted peer (indicating the presence of TLKs)
981 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 to:self.remotePeer1 zoneID:self.keychainZoneID];
983 NSDateComponents* offset = [[NSDateComponents alloc] init];
985 NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0];
986 for(CKRecord* record in self.keychainZone.currentDatabase.allValues) {
987 if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) {
988 record.creationDate = updateTime;
989 record.modificationDate = updateTime;
993 self.keychainZone.flag = true;
994 [self startCKKSSubsystem];
996 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], @"Key state should become 'waitfortlk'");
998 XCTAssertTrue(self.keychainZone.flag, "Zone flag should not have been reset to false");
1001 - (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToVeryRecentUntrustedTLKShare {
1002 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1004 // 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.
1005 CKKSSOSSelfPeer* untrustedPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"untrusted-peer"
1006 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1007 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1008 viewList:self.managedViewList];
1009 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:untrustedPeer to:untrustedPeer zoneID:self.keychainZoneID];
1011 NSDateComponents* offset = [[NSDateComponents alloc] init];
1013 NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0];
1014 for(CKRecord* record in self.keychainZone.currentDatabase.allValues) {
1015 if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) {
1016 record.creationDate = updateTime;
1017 record.modificationDate = updateTime;
1021 self.keychainZone.flag = true;
1022 [self startCKKSSubsystem];
1024 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], @"Key state should become 'waitfortlk'");
1025 XCTAssertTrue(self.keychainZone.flag, "Zone flag should not have been reset to false");
1027 // And ensure it doesn't go on to 'reset'
1028 XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateResettingZone] wait:100*NSEC_PER_MSEC], @"Key state should not become 'resetzone'");
1031 - (void)testResetCloudKitZoneFromWaitForTLKDueToUntustedTLKShareNotRecentEnough {
1032 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1034 // CKKS shouldn't reset this zone, due to a recent TLK Share (indicating the presence of TLKs)
1035 CKKSSOSSelfPeer* untrustedPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"untrusted-peer"
1036 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1037 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1038 viewList:self.managedViewList];
1039 [self putTLKShareInCloudKit:self.keychainZoneKeys.tlk from:untrustedPeer to:untrustedPeer zoneID:self.keychainZoneID];
1041 NSDateComponents* offset = [[NSDateComponents alloc] init];
1043 NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0];
1044 for(CKRecord* record in self.keychainZone.currentDatabase.allValues) {
1045 if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) {
1046 record.creationDate = updateTime;
1047 record.modificationDate = updateTime;
1051 self.silentZoneDeletesAllowed = true;
1052 self.keychainZone.flag = true;
1053 [self startCKKSSubsystem];
1054 [self performOctagonTLKUpload:self.ckksViews];
1056 // Then we should reset.
1057 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1058 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
1060 // And the zone should have been cleared and re-made
1061 XCTAssertFalse(self.keychainZone.flag, "Zone flag should have been reset to false");
1064 - (void)testNoSOSSelfEncryptionKeys {
1065 // If you lose your local encryption keys, CKKS should do something reasonable
1067 // Test also starts with the TLK shared to all trusted peers from peer1
1068 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1069 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1070 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1072 // But, we lost our local keys :(
1073 CKKSSOSSelfPeer* oldSelfPeer = self.mockSOSAdapter.selfPeer;
1075 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecParam description:@"injected test failure"];
1077 // CKKS subsystem should realize that it can't read the shares it has, and enter waitfortlk
1078 [self startCKKSSubsystem];
1079 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortlk'");
1081 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1082 [self waitForCKModifications];
1084 // Fetching status should be quick
1085 XCTestExpectation* callbackOccurs = [self expectationWithDescription:@"callback-occurs"];
1086 [self.ckksControl rpcStatus:@"keychain" reply:^(NSArray<NSDictionary*>* result, NSError* error) {
1087 XCTAssertNil(error, "should be no error fetching status for keychain");
1088 [callbackOccurs fulfill];
1090 [self waitForExpectations:@[callbackOccurs] timeout:20];
1092 // But, if by some miracle those keys come back, CKKS should be able to recover
1093 // It'll also upload itself a TLK share
1094 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1096 self.mockSOSAdapter.selfPeer = oldSelfPeer;
1097 self.mockSOSAdapter.selfPeerError = nil;
1099 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
1100 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready''");
1102 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1103 [self waitForCKModifications];
1106 - (void)testNoSOSSelfEncryptionKeysDuringCreation {
1107 // If you lose your local encryption keys, CKKS should do something reasonable
1109 // But, things don't exist :(
1110 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecParam description:@"injected test failure"];
1112 [self startCKKSSubsystem];
1114 NSMutableArray<CKKSResultOperation<CKKSKeySetProviderOperationProtocol>*>* keysetOps = [NSMutableArray array];
1115 for(CKKSKeychainView* view in self.ckksViews) {
1116 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLKCreation] wait:40*NSEC_PER_SEC], @"key state should enter 'waitfortlkcreation' (view %@)", view);
1117 [keysetOps addObject: [view findKeySet:NO]];
1120 // Now that we've kicked them all off, wait for them to not crash
1121 for(CKKSKeychainView* view in self.ckksViews) {
1122 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateError] wait:40*NSEC_PER_SEC], @"key state should enter 'error'");
1126 // Fetching status should be quick
1127 XCTestExpectation* callbackOccurs = [self expectationWithDescription:@"callback-occurs"];
1128 [self.ckksControl rpcStatus:@"keychain" reply:^(NSArray<NSDictionary*>* result, NSError* error) {
1129 XCTAssertNil(error, "should be no error fetching status for keychain");
1130 [callbackOccurs fulfill];
1132 [self waitForExpectations:@[callbackOccurs] timeout:20];
1134 // But, if by some miracle those keys come back, CKKS should be able to recover
1135 // It'll also upload itself a TLK share
1136 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1138 self.mockSOSAdapter.selfPeer = oldSelfPeer;
1139 self.mockSOSAdapter.selfPeerError = nil;
1141 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
1142 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready''");
1144 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1145 [self waitForCKModifications];*/
1149 - (void)testNoOctagonSelfEncryptionKeys {
1150 // If you lose your local encryption keys, CKKS should do something reasonable
1152 // Test also starts with the TLK shared to all trusted peers from peer1
1153 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1154 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1155 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1158 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1159 [self startCKKSSubsystem];
1160 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1162 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1163 [self waitForCKModifications];
1165 // But, we lose our local keys :(
1166 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:TrustedPeersHelperErrorDomain
1168 description:@"injected test failure (Octagon key loss)"];
1170 // Kick off TLK checking
1171 [self.keychainView trustedPeerSetChanged:self.mockSOSAdapter];
1172 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'untrusted'");
1174 // Fetching status should be quick
1175 XCTestExpectation* callbackOccurs = [self expectationWithDescription:@"callback-occurs"];
1176 [self.ckksControl rpcStatus:@"keychain" reply:^(NSArray<NSDictionary*>* result, NSError* error) {
1177 XCTAssertNil(error, "should be no error fetching status for keychain");
1178 [callbackOccurs fulfill];
1180 [self waitForExpectations:@[callbackOccurs] timeout:20];
1182 // and it should pause, and not spin infinitely
1183 XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateInitialized] wait:2*NSEC_PER_SEC], "Key state should not become 'initialized'");
1186 - (void)testLoseTrustIfSelfPeerNotTrusted {
1187 // If you decide you don't trust yourself, CKKS should do something reasonable
1189 // Test also starts with the TLK shared to all trusted peers from peer1
1190 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1191 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1192 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1194 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1195 [self startCKKSSubsystem];
1196 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1198 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1199 [self waitForCKModifications];
1201 self.mockSOSAdapter.excludeSelfPeerFromTrustSet = true;
1203 // Kick off TLK checking
1204 [self.keychainView trustedPeerSetChanged:self.mockSOSAdapter];
1205 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'untrusted'");
1207 // and it should pause, and not spin infinitely
1208 XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateInitialized] wait:2*NSEC_PER_SEC], "Key state should not become 'initialized'");
1211 - (void)testDontLoseTrustIfNonessentialTrustStateIsBroken {
1212 // if a non-essential but broken peer provider exists, CKKS shouldn't pay it any mind
1214 // Test also starts with the TLK shared to all trusted peers from peer1
1215 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1216 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1217 [self saveTLKSharesInLocalDatabase:self.keychainZoneID];
1220 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1221 [self startCKKSSubsystem];
1222 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1224 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1225 [self waitForCKModifications];
1227 // To fake this, make another mock SOS adapter, but make it nonessential
1228 CKKSSOSSelfPeer* brokenSelfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"local-peer"
1229 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1230 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
1231 viewList:self.managedViewList];
1232 CKKSMockSOSPresentAdapter* brokenAdapter = [[CKKSMockSOSPresentAdapter alloc] initWithSelfPeer:brokenSelfPeer
1233 trustedPeers:[NSSet set]
1235 brokenAdapter.excludeSelfPeerFromTrustSet = true;
1237 [self.keychainView endTrustedOperation];
1238 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");
1240 [self.keychainView beginTrustedOperation:@[self.mockSOSAdapter, brokenAdapter]
1241 suggestTLKUpload:self.suggestTLKUpload
1242 requestPolicyCheck:self.requestPolicyCheck];
1244 // CKKS should ignore the non-essential and broken peer adapter
1245 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready'");
1248 - (void)testTLKSharesForOctagonPeer {
1249 // Launch first for SOS only
1250 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1251 [self saveTLKMaterialToKeychain:self.keychainZoneID];
1253 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
1254 [self startCKKSSubsystem];
1256 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
1257 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1258 [self waitForCKModifications];
1260 [self.keychainView endTrustedOperation];
1261 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");
1263 // Spin up Octagon. We expect an upload of 3 TLK shares, this time for the octagon peer
1264 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
1265 [self.keychainView beginTrustedOperation:@[self.mockSOSAdapter, self.mockOctagonAdapter]
1266 suggestTLKUpload:self.suggestTLKUpload
1267 requestPolicyCheck:self.requestPolicyCheck];
1268 [self.mockOctagonAdapter sendTrustedPeerSetChangedUpdate];
1270 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1271 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
1274 - (void)testQuiesceOnTrustLossDuringInitialFetch {
1275 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
1276 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1277 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
1279 [self holdCloudKitFetches];
1281 [self startCKKSSubsystem];
1282 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateFetch] wait:20*NSEC_PER_SEC], "Key state should become 'fetch'");
1284 [self endSOSTrustedOperationForAllViews];
1286 [self releaseCloudKitFetchHold];
1287 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");