]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSTLKSharingTests.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSTLKSharingTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <CloudKit/CloudKit.h>
27 #import <XCTest/XCTest.h>
28 #import <OCMock/OCMock.h>
29 #import <SecurityFoundation/SFDigestOperation.h>
30 #import <SecurityFoundation/SFKey.h>
31 #import <SecurityFoundation/SFKey_Private.h>
32 #import <SecurityFoundation/SFIdentity.h>
33
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"
43
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"
48
49 #import "keychain/ckks/tests/CKKSMockOctagonAdapter.h"
50
51 @class OctagonSelfPeer;
52
53 @interface CloudKitKeychainSyncingTLKSharingTests : CloudKitKeychainSyncingTestsBase
54 @property CKKSSOSSelfPeer* remotePeer1;
55 @property CKKSSOSPeer* remotePeer2;
56
57 @property OctagonSelfPeer* selfOTPeer1;
58 @property id<CKKSPeer> remoteOTPeer2;
59
60 @property CKKSSOSSelfPeer* untrustedPeer;
61
62 @property (nullable) NSMutableSet<id<CKKSSelfPeer>>* pastSelfPeers;
63
64 @end
65
66 @implementation CloudKitKeychainSyncingTLKSharingTests
67
68 - (void)setUp {
69 [super setUp];
70
71 self.pastSelfPeers = [NSMutableSet set];
72
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];
77
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];
82
83 // Local SOS trusts these peers
84 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
85 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer2];
86
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];
91
92 /*
93 * Octagon glue
94 */
95 SFKeyPair *signingKeyPair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
96 SFKeyPair *encrytionKeyPair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
97
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;
102
103 self.mockOctagonAdapter = [[CKKSMockOctagonAdapter alloc] initWithSelfPeer:self.selfOTPeer1
104 trustedPeers:[NSSet set]
105 essential:YES];
106 self.mockOctagonAdapter.selfViewList = self.managedViewList;
107
108 }
109
110 - (void)tearDown {
111 self.pastSelfPeers = nil;
112 self.remotePeer1 = nil;
113 self.remotePeer2 = nil;
114 self.untrustedPeer = nil;
115 self.selfOTPeer1 = nil;
116 self.remoteOTPeer2 = nil;
117
118 [super tearDown];
119 }
120
121 - (void)testAcceptExistingTLKSharedKeyHierarchy {
122 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
123 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
124
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];
127
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");
132
133 OCMVerifyAllWithDelay(self.mockDatabase, 20);
134
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");
140
141 NSError* error = nil;
142
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");
146
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");
150
151 return false;
152 }];
153 }
154
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];
159
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];
162
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];
169
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");
177 return TRUE;
178 }];
179 [self startCKKSSubsystem];
180 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
181
182 OCMVerifyAllWithDelay(self.mockDatabase, 20);
183
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");
189
190 NSError* error = nil;
191
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");
195
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");
199
200 return false;
201 }];
202 }
203
204 - (void)testAcceptExistingTLKSharedKeyHierarchyAndUse {
205 // Test starts with nothing in database, but one in our fake CloudKit.
206 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
207
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];
210
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");
215
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);
221
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"
226 viewHint:nil
227 access:(id)kSecAttrAccessibleWhenUnlocked
228 expecting:errSecSuccess
229 message:@"Adding class A item"];
230 OCMVerifyAllWithDelay(self.mockDatabase, 20);
231 }
232
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
235
236 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
237 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
238
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];
242
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");
247
248 OCMVerifyAllWithDelay(self.mockDatabase, 20);
249 [self waitForCKModifications];
250
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");
256
257 NSError* error = nil;
258 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:strongSelf.keychainZoneKeys.tlk
259 as:strongSelf.mockSOSAdapter.selfPeer
260 to:strongSelf.mockSOSAdapter.selfPeer
261 epoch:-1
262 poisoned:0
263 error:&error];
264 XCTAssertNil(error, "Shouldn't be an error creating a share");
265 XCTAssertNotNil(share, "Should be able to create share");
266
267 CKRecord* newRecord = [share CKRecordWithZoneID:strongSelf.keychainZoneID];
268 XCTAssertNotNil(newRecord, "Should be able to create a CKRecord");
269
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");
273
274 XCTAssertEqualObjects(cloudKitRecord.recordChangeTag, newRecord.recordChangeTag, "Change tags on existing and new records should match");
275
276 return false;
277 }];
278 }
279
280 - (void)testReceiveTLKShareRecordsAndDeletes {
281 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
282 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
283
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];
286
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");
291
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);
295
296
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
301 to:self.remotePeer1
302 epoch:-1
303 poisoned:0
304 error:&error];
305 XCTAssertNil(error, "Should have been no error sharing a CKKSKey");
306 XCTAssertNotNil(share, "Should be able to create a share");
307
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];
313
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");
319 return true;
320 }];
321
322 // Delete the record in CloudKit...
323 [self.keychainZone deleteCKRecordIDFromZone:shareCKRecord.recordID];
324 [self.keychainView notifyZoneChange:nil];
325 [self.keychainView waitForFetchAndIncomingQueueProcessing];
326
327 // Should be gone now.
328 [self.keychainView dispatchSync:^bool {
329 NSError* blockerror = nil;
330 CKKSTLKShareRecord* localshare = [CKKSTLKShareRecord tryFromDatabaseFromCKRecordID:shareCKRecord.recordID error:&blockerror];
331
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");
334
335 return true;
336 }];
337 }
338
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];
343
344 // Spin up CKKS subsystem.
345 [self startCKKSSubsystem];
346
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");
349
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];
353
354 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
355 [self.keychainView notifyZoneChange:nil];
356 [self.keychainView waitForFetchAndIncomingQueueProcessing];
357
358 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
359
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);
365
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"
370 viewHint:nil
371 access:(id)kSecAttrAccessibleWhenUnlocked
372 expecting:errSecSuccess
373 message:@"Adding class A item"];
374 OCMVerifyAllWithDelay(self.mockDatabase, 20);
375 }
376
377 - (void)testReceiveTLKShareWhileLocked {
378 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
379 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
380
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];
383
384 self.aksLockState = true;
385 [self.lockStateTracker recheck];
386
387 // Spin up CKKS subsystem.
388 [self startCKKSSubsystem];
389
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");
392
393 // Now unlock things. We expect a TLKShare upload.
394 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
395
396 self.aksLockState = false;
397 [self.lockStateTracker recheck];
398
399 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
400 OCMVerifyAllWithDelay(self.mockDatabase, 20);
401 }
402
403 - (void)testUploadTLKSharesForExistingHierarchy {
404 // Test starts with key material locally and in CloudKit.
405 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
406 [self saveTLKMaterialToKeychain:self.keychainZoneID];
407
408 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
409 [self startCKKSSubsystem];
410
411 OCMVerifyAllWithDelay(self.mockDatabase, 20);
412 }
413
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];
418
419 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
420 [self startCKKSSubsystem];
421
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];
425
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");
431
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];
436 }
437 }
438
439 return true;
440 }];
441
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];
446
447 OCMVerifyAllWithDelay(self.mockDatabase, 20);
448 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
449 }
450
451 - (void)testHandleExternalSharedTLKRoll {
452 // Test starts with key material locally and in CloudKit.
453 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
454 [self saveTLKMaterialToKeychain:self.keychainZoneID];
455
456 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
457 [self startCKKSSubsystem];
458
459 OCMVerifyAllWithDelay(self.mockDatabase, 20);
460 [self waitForCKModifications];
461
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];
465
466 // CKKS will share the TLK back to itself
467 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
468
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];
475
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"];
480
481 OCMVerifyAllWithDelay(self.mockDatabase, 20);
482 }
483
484 - (void)testUploadTLKSharesForExternalTLKRollWithoutShares {
485 // Test starts with key material locally and in CloudKit.
486 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
487 [self saveTLKMaterialToKeychain:self.keychainZoneID];
488
489 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
490 [self startCKKSSubsystem];
491
492 OCMVerifyAllWithDelay(self.mockDatabase, 20);
493
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];
497
498 // Wait for that modification to finish before changing CK data
499 [self waitForCKModifications];
500
501 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
502 [self saveTLKMaterialToKeychain:self.keychainZoneID];
503
504 // Trigger a notification
505 [self.keychainView notifyZoneChange:nil];
506
507 OCMVerifyAllWithDelay(self.mockDatabase, 20);
508 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
509
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"];
514
515 OCMVerifyAllWithDelay(self.mockDatabase, 20);
516 }
517
518 - (void)testRecoverFromTLKShareUploadFailure {
519 // Test starts with key material locally and in CloudKit.
520 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
521 [self saveTLKMaterialToKeychain:self.keychainZoneID];
522
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];
527 }];
528 [self startCKKSSubsystem];
529
530 OCMVerifyAllWithDelay(self.mockDatabase, 20);
531 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
532 }
533
534 - (void)testFillInMissingPeerShares {
535 // Test starts with nothing in database, but one in our fake CloudKit.
536 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
537
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];
541
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");
546
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);
552
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"
557 viewHint:nil
558 access:(id)kSecAttrAccessibleWhenUnlocked
559 expecting:errSecSuccess
560 message:@"Adding class A item"];
561 OCMVerifyAllWithDelay(self.mockDatabase, 20);
562 }
563
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];
569
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];
572
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");
576 }
577
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];
582
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];
587
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");
591
592 // Wait to be sure we really get into that state
593 [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
594
595 // Now, trust the previously-untrusted peer
596 [self.mockSOSAdapter.trustedPeers addObject: self.untrustedPeer];
597 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
598
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];
601
602 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
603
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);
609 }
610
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];
615
616 [self startCKKSSubsystem];
617 [self performOctagonTLKUpload:self.ckksViews];
618
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);
623
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];
628
629 OCMVerifyAllWithDelay(self.mockDatabase, 20);
630 [self waitForCKModifications];
631
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];
636
637 // CKKS should not upload a tlk share for this peer
638 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer2];
639 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
640
641 [self.keychainView waitUntilAllOperationsAreFinished];
642 }
643
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];
648
649 [self startCKKSSubsystem];
650 [self performOctagonTLKUpload:self.ckksViews];
651
652 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
653
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);
658
659 // Now, lock.
660 self.aksLockState = true;
661 [self.lockStateTracker recheck];
662
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];
666
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'");
669
670 // And do them.
671 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
672 self.aksLockState = false;
673 [self.lockStateTracker recheck];
674
675 OCMVerifyAllWithDelay(self.mockDatabase, 20);
676
677 // and return to ready
678 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should become 'ready'");
679 }
680
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];
685
686 [self startCKKSSubsystem];
687 [self performOctagonTLKUpload:self.ckksViews];
688
689 OCMVerifyAllWithDelay(self.mockDatabase, 20);
690 [self waitForCKModifications];
691
692 // Hold the TLK share modification
693 [self holdCloudKitModifications];
694
695 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
696 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
697 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
698
699 OCMVerifyAllWithDelay(self.mockDatabase, 20);
700
701 // While CloudKit is hanging the write, add an item
702 [self addGenericPassword: @"data" account: @"account-delete-me"];
703
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];
708
709 OCMVerifyAllWithDelay(self.mockDatabase, 20);
710 }
711
712 - (void)testSendNewTLKSharesOnTrustSetRemoval {
713 // Not implemented. Trust set removal demands a key roll, but let's not get ahead of ourselves...
714 }
715
716 - (void)testWaitForTLKWithMissingKeys {
717 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
718 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
719
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];
722
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];
729
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);
734 }
735
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
739
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];
744
745 OCMVerifyAllWithDelay(self.mockDatabase, 20);
746 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
747
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");
755 return TRUE;
756 }];
757
758 self.remotePeer1.encryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
759 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
760
761 OCMVerifyAllWithDelay(self.mockDatabase, 20);
762 [self waitForCKModifications];
763
764 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
765 }
766
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
771
772 // For this test, only have one peer
773 self.mockSOSAdapter.trustedPeers = [NSMutableSet setWithObject:self.remotePeer1];
774
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];
779
780 // BUT, remotePeer1 has rolled its signing key
781 self.remotePeer1.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
782
783 [self startCKKSSubsystem];
784
785 OCMVerifyAllWithDelay(self.mockDatabase, 20);
786 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "Key state should become waitfortlk");
787
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");
795 return TRUE;
796 }];
797
798 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
799 [self.keychainView notifyZoneChange:nil];
800
801 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
802
803 OCMVerifyAllWithDelay(self.mockDatabase, 20);
804 [self waitForCKModifications];
805 }
806
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
810
811 // For this test, only have one peer
812 self.mockSOSAdapter.trustedPeers = [NSMutableSet setWithObject:self.remotePeer1];
813
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");
823
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");
831 return TRUE;
832 }];
833
834 self.remotePeer1.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
835 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
836
837 OCMVerifyAllWithDelay(self.mockDatabase, 20);
838 [self waitForCKModifications];
839
840 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
841 }
842
843 - (void)testSendNewTLKShareToPeerOnDisappearanceOfPeerKeys {
844 // If a CKKS peer deletes its own octagon keys (BUT WHY), local CKKS should be able to respond
845
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");
854
855 // Now, peer 1 updates its keys (to be nil). Local peer should re-send TLKShares to peer2.
856
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");
863 return TRUE;
864 }];
865
866 CKKSSOSPeer* brokenRemotePeer1 = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.remotePeer1.peerID
867 encryptionPublicKey:nil
868 signingPublicKey:nil
869 viewList:self.managedViewList];
870 [self.mockSOSAdapter.trustedPeers removeObject:self.remotePeer1];
871 [self.mockSOSAdapter.trustedPeers addObject:brokenRemotePeer1];
872 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
873
874 OCMVerifyAllWithDelay(self.mockDatabase, 20);
875 [self waitForCKModifications];
876
877 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
878 }
879
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
883
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");
893
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
896
897 XCTestExpectation *peer1Share = [self expectationWithDescription:@"share uploaded for peer1"];
898 XCTestExpectation *peer2Share = [self expectationWithDescription:@"share uploaded for peer2"];
899
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");
906 }
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");
910 }
911
912 XCTAssertEqualObjects(share.senderPeerID, self.mockSOSAdapter.selfPeer.peerID, "Sender of TLKShare should match current self");
913 return TRUE;
914 }];
915
916 CKKSSOSPeer* brokenRemotePeer1 = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.remotePeer1.peerID
917 encryptionPublicKey:self.remotePeer1.publicEncryptionKey
918 signingPublicKey:nil
919 viewList:self.managedViewList];
920 [self.mockSOSAdapter.trustedPeers removeObject:self.remotePeer1];
921 [self.mockSOSAdapter.trustedPeers addObject:brokenRemotePeer1];
922 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
923
924 OCMVerifyAllWithDelay(self.mockDatabase, 20);
925 [self waitForCKModifications];
926 [self waitForExpectations:@[peer1Share, peer2Share] timeout:5];
927
928 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
929 }
930
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
934
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");
944
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");
955 return TRUE;
956 }];
957
958 self.mockSOSAdapter.selfPeer.encryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
959 self.pastSelfPeers = [NSMutableSet set];
960 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
961
962 OCMVerifyAllWithDelay(self.mockDatabase, 20);
963 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
964
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");
975 return TRUE;
976 }];
977
978 self.mockSOSAdapter.selfPeer.signingKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
979 self.pastSelfPeers = [NSMutableSet set];
980 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
981
982 OCMVerifyAllWithDelay(self.mockDatabase, 20);
983 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
984 }
985
986 - (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToRecentTLKShare {
987 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
988
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];
991
992 NSDateComponents* offset = [[NSDateComponents alloc] init];
993 [offset setDay:-5];
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;
999 }
1000 }
1001
1002 self.keychainZone.flag = true;
1003 [self startCKKSSubsystem];
1004
1005 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], @"Key state should become 'waitfortlk'");
1006
1007 XCTAssertTrue(self.keychainZone.flag, "Zone flag should not have been reset to false");
1008 }
1009
1010 - (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToVeryRecentUntrustedTLKShare {
1011 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1012
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];
1019
1020 NSDateComponents* offset = [[NSDateComponents alloc] init];
1021 [offset setDay:-2];
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;
1027 }
1028 }
1029
1030 self.keychainZone.flag = true;
1031 [self startCKKSSubsystem];
1032
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");
1035
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'");
1038 }
1039
1040 - (void)testResetCloudKitZoneFromWaitForTLKDueToUntustedTLKShareNotRecentEnough {
1041 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1042
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];
1049
1050 NSDateComponents* offset = [[NSDateComponents alloc] init];
1051 [offset setDay:-5];
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;
1057 }
1058 }
1059
1060 self.silentZoneDeletesAllowed = true;
1061 self.keychainZone.flag = true;
1062 [self startCKKSSubsystem];
1063 [self performOctagonTLKUpload:self.ckksViews];
1064
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'");
1068
1069 // And the zone should have been cleared and re-made
1070 XCTAssertFalse(self.keychainZone.flag, "Zone flag should have been reset to false");
1071 }
1072
1073 - (void)testNoSOSSelfEncryptionKeys {
1074 // If you lose your local encryption keys, CKKS should do something reasonable
1075
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];
1080
1081 // But, we lost our local keys :(
1082 CKKSSOSSelfPeer* oldSelfPeer = self.mockSOSAdapter.selfPeer;
1083
1084 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecParam description:@"injected test failure"];
1085
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'");
1089
1090 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1091 [self waitForCKModifications];
1092
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];
1098 }];
1099 [self waitForExpectations:@[callbackOccurs] timeout:20];
1100
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];
1104
1105 self.mockSOSAdapter.selfPeer = oldSelfPeer;
1106 self.mockSOSAdapter.selfPeerError = nil;
1107
1108 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
1109 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready''");
1110
1111 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1112 [self waitForCKModifications];
1113 }
1114
1115 - (void)testNoSOSSelfEncryptionKeysDuringCreation {
1116 // If you lose your local encryption keys, CKKS should do something reasonable
1117
1118 // But, things don't exist :(
1119 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecParam description:@"injected test failure"];
1120
1121 [self startCKKSSubsystem];
1122
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]];
1127 }
1128
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'");
1132 }
1133
1134 /*
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];
1140 }];
1141 [self waitForExpectations:@[callbackOccurs] timeout:20];
1142
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];
1146
1147 self.mockSOSAdapter.selfPeer = oldSelfPeer;
1148 self.mockSOSAdapter.selfPeerError = nil;
1149
1150 [self.mockSOSAdapter sendSelfPeerChangedUpdate];
1151 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become 'ready''");
1152
1153 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1154 [self waitForCKModifications];*/
1155 }
1156
1157
1158 - (void)testNoOctagonSelfEncryptionKeys {
1159 // If you lose your local encryption keys, CKKS should do something reasonable
1160
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];
1165
1166
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'");
1170
1171 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1172 [self waitForCKModifications];
1173
1174 // But, we lose our local keys :(
1175 self.mockSOSAdapter.selfPeerError = [NSError errorWithDomain:TrustedPeersHelperErrorDomain
1176 code:1
1177 description:@"injected test failure (Octagon key loss)"];
1178
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'");
1182
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];
1188 }];
1189 [self waitForExpectations:@[callbackOccurs] timeout:20];
1190
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'");
1193 }
1194
1195 - (void)testLoseTrustIfSelfPeerNotTrusted {
1196 // If you decide you don't trust yourself, CKKS should do something reasonable
1197
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];
1202
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'");
1206
1207 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1208 [self waitForCKModifications];
1209
1210 self.mockSOSAdapter.excludeSelfPeerFromTrustSet = true;
1211
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'");
1215
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'");
1218 }
1219
1220 - (void)testDontLoseTrustIfNonessentialTrustStateIsBroken {
1221 // if a non-essential but broken peer provider exists, CKKS shouldn't pay it any mind
1222
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];
1227
1228
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'");
1232
1233 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1234 [self waitForCKModifications];
1235
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]
1243 essential:NO];
1244 brokenAdapter.excludeSelfPeerFromTrustSet = true;
1245
1246 [self.keychainView endTrustedOperation];
1247 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");
1248
1249 [self.keychainView beginTrustedOperation:@[self.mockSOSAdapter, brokenAdapter] suggestTLKUpload:self.suggestTLKUpload];
1250
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'");
1253 }
1254
1255 - (void)testTLKSharesForOctagonPeer {
1256 // Launch first for SOS only
1257 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
1258 [self saveTLKMaterialToKeychain:self.keychainZoneID];
1259
1260 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
1261 [self startCKKSSubsystem];
1262
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];
1266
1267 [self.keychainView endTrustedOperation];
1268 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] wait:20*NSEC_PER_SEC], "Key state should become 'waitfortrust'");
1269
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];
1274
1275 OCMVerifyAllWithDelay(self.mockDatabase, 20);
1276 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
1277 }
1278
1279 @end
1280
1281 #endif // OCTAGON