]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSServerValidationRecoveryTests.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSServerValidationRecoveryTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <CloudKit/CloudKit.h>
27 #import <XCTest/XCTest.h>
28 #import <OCMock/OCMock.h>
29
30 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
31 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
32 #import "keychain/ckks/CKKS.h"
33 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
34 #import "keychain/ckks/CKKSKey.h"
35 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
36 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
37
38 #import "keychain/ckks/tests/MockCloudKit.h"
39 #import "keychain/ckks/tests/CKKSTests.h"
40
41 @interface CloudKitKeychainSyncingServerValidationRecoveryTests : CloudKitKeychainSyncingTestsBase
42 @end
43
44 @implementation CloudKitKeychainSyncingServerValidationRecoveryTests
45
46 /* Tests for CKKSServerUnexpectedSyncKeyInChain */
47
48 - (void)testRecoverFromWrongClassACurrentKeyPointersOnStartup {
49 // The current key pointers in cloudkit should always point directly under the top TLK.
50
51 // Test starts with a broken key hierarchy in our fake CloudKit, and the TLK already arrived.
52 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
53 [self saveTLKMaterialToKeychain:self.keychainZoneID];
54
55 CKReference* oldClassAKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassAPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
56 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
57 [self saveTLKMaterialToKeychain:self.keychainZoneID];
58 CKReference* newClassAKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassAPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
59
60 // Break the reference
61 self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassAPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey] = oldClassAKey;
62 self.keychainZoneKeys.currentClassAPointer.currentKeyUUID = oldClassAKey.recordID.recordName;
63
64 // CKKS should then fix the pointers and give itself a TLK share, but not update any keys
65 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 1 zoneID:self.keychainZoneID];
66
67 // And then upload the record as normal
68 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
69 checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
70 [self addGenericPassword:@"asdf"
71 account:@"account-class-A"
72 viewHint:nil
73 access:(id)kSecAttrAccessibleWhenUnlocked
74 expecting:errSecSuccess
75 message:@"Adding class A item"];
76
77 // Spin up CKKS subsystem.
78 [self startCKKSSubsystem];
79
80 OCMVerifyAllWithDelay(self.mockDatabase, 20);
81
82 [self waitForCKModifications];
83 XCTAssertEqualObjects(newClassAKey, self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassAPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class A key reference");
84 }
85
86 - (void)testRecoverFromWrongClassCCurrentKeyPointersOnStartup {
87 // The current key pointers in cloudkit should always point directly under the top TLK.
88
89 // Test starts with a broken key hierarchy in our fake CloudKit, and the TLK already arrived.
90 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
91 [self saveTLKMaterialToKeychain:self.keychainZoneID];
92
93 CKReference* oldClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
94 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
95 [self saveTLKMaterialToKeychain:self.keychainZoneID];
96 CKReference* newClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
97
98 // Break the reference
99 self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey] = oldClassCKey;
100 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
101
102 // CKKS should then fix the pointers and its TLK shares, but not update any keys
103 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:1 tlkShareRecords:1 zoneID:self.keychainZoneID];
104
105 // And then upload the record as normal
106 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
107 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
108 [self addGenericPassword: @"data" account: @"account-delete-me"];
109
110 // Spin up CKKS subsystem.
111 [self startCKKSSubsystem];
112
113 OCMVerifyAllWithDelay(self.mockDatabase, 20);
114
115 [self waitForCKModifications];
116 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
117 }
118
119 - (void)testRecoverFromWrongClassCCurrentKeyPointersOnNotification {
120 // The current key pointers in cloudkit should always point directly under the top TLK.
121
122 // Test starts with a good key hierarchy in our fake CloudKit, and the TLK already arrived.
123 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
124 [self saveTLKMaterialToKeychain:self.keychainZoneID];
125 [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
126
127 CKReference* oldClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
128
129 // Spin up CKKS subsystem.
130 [self startCKKSSubsystem];
131
132 // Uploading works
133 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
134 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
135 [self addGenericPassword: @"data" account: @"account-delete-me"];
136 OCMVerifyAllWithDelay(self.mockDatabase, 20);
137
138 // Break the key hierarchy
139 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
140 [self saveTLKMaterialToKeychain:self.keychainZoneID];
141
142 CKReference* newClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
143 self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey] = oldClassCKey;
144 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
145
146 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
147
148 // CKKS should then fix the pointers and give itself a new TLK share record, but not update any keys
149 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:1 tlkShareRecords:1 zoneID:self.keychainZoneID];
150 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
151 OCMVerifyAllWithDelay(self.mockDatabase, 20);
152
153 // And then upload the item as usual
154 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
155 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
156 [self addGenericPassword: @"data" account: @"account-delete-me-2"];
157 OCMVerifyAllWithDelay(self.mockDatabase, 20);
158
159 [self waitForCKModifications];
160 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
161 }
162
163 - (void)testRecoverFromWrongClassCCurrentKeyPointersOnNotificationFixRejected {
164 // The current key pointers in cloudkit should always point directly under the top TLK.
165
166 // Test starts with a good key hierarchy in our fake CloudKit, and the TLK already arrived.
167 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
168 [self saveTLKMaterialToKeychain:self.keychainZoneID];
169 [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
170
171 CKRecordID* classCPointerRecordID = self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID;
172
173 CKReference* oldClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
174
175 // Spin up CKKS subsystem.
176 [self startCKKSSubsystem];
177
178 // Uploading works
179 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
180 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
181 [self addGenericPassword: @"data" account: @"account-delete-me"];
182 OCMVerifyAllWithDelay(self.mockDatabase, 20);
183
184 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should have arrived at ready");
185 [self waitForCKModifications];
186
187 // Break the key hierarchy. The TLK will arrive via SOS later in this test.
188 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
189
190 CKReference* newClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
191 CKRecord* classCPointer = self.keychainZone.currentDatabase[classCPointerRecordID];
192
193 CKRecord* brokenClassCPointer = [classCPointer copy];
194 brokenClassCPointer[SecCKRecordParentKeyRefKey] = oldClassCKey;
195 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
196 [self.keychainZone addCKRecordToZone:brokenClassCPointer];
197
198 self.silentFetchesAllowed = false;
199 [self expectCKFetchAndRunBeforeFinished: ^{
200 // Directly after CKKS fetches, we should fix up the pointers to be right again
201 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = newClassCKey.recordID.recordName;
202 [self.keychainZone addToZone: classCPointer];
203 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
204 self.silentFetchesAllowed = true;
205 }];
206
207 // CKKS should try to fix the pointers, but be rejected (since someone else has already fixed them)
208 // It should not try to modify the pointers again, but it should give itself the new TLK
209 [self expectCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID];
210 [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
211
212 [self saveTLKMaterialToKeychain:self.keychainZoneID];
213 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
214 OCMVerifyAllWithDelay(self.mockDatabase, 20);
215
216 // And then use the 'new' key as it should
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-2"];
220 OCMVerifyAllWithDelay(self.mockDatabase, 20);
221
222 [self waitForCKModifications];
223 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
224 }
225
226 - (void)testRecoverFromWrongClassCCurrentKeyPointersOnRecordWrite {
227 // The current key pointers in cloudkit should always point directly under the top TLK.
228
229 // Test starts with a good but rolled key hierarchy in our fake CloudKit, and the TLK already arrived.
230 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
231 [self saveTLKMaterialToKeychain:self.keychainZoneID];
232
233 CKRecordID* classCPointerRecordID = self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID;
234 CKReference* oldClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
235
236 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
237 [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
238 [self saveTLKMaterialToKeychain:self.keychainZoneID];
239
240 // Spin up CKKS subsystem.
241 [self startCKKSSubsystem];
242
243 // Uploading works
244 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
245 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
246 [self addGenericPassword: @"data" account: @"account-delete-me"];
247 OCMVerifyAllWithDelay(self.mockDatabase, 20);
248 [self waitForCKModifications];
249
250 // Now, break the class C pointer, but don't tell CKKS
251 CKReference* newClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
252 CKRecord* classCPointer = self.keychainZone.currentDatabase[classCPointerRecordID];
253
254 CKRecord* brokenClassCPointer = [classCPointer copy];
255 brokenClassCPointer[SecCKRecordParentKeyRefKey] = oldClassCKey;
256 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
257 [self.keychainZone addCKRecordToZone:brokenClassCPointer];
258
259 // CKKS should receive a key hierarchy error, since it's wrong in CloudKit
260 // It should then fix the pointers and retry the upload
261 [self expectCKReceiveSyncKeyHierarchyError:self.keychainZoneID];
262 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 zoneID:self.keychainZoneID];
263
264 // And then use the 'new' key as it should
265 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
266 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
267 [self addGenericPassword: @"data" account: @"account-delete-me-2"];
268 OCMVerifyAllWithDelay(self.mockDatabase, 20);
269
270 [self waitForCKModifications];
271 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
272 }
273
274 @end
275
276 #endif // OCTAGON