]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSServerValidationRecoveryTests.m
Security-58286.31.2.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, but not update any keys
65 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 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, 8);
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, but not update any keys
103 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 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, 8);
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
126 CKReference* oldClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
127
128 // Spin up CKKS subsystem.
129 [self startCKKSSubsystem];
130
131 // Uploading works
132 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
133 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
134 [self addGenericPassword: @"data" account: @"account-delete-me"];
135 OCMVerifyAllWithDelay(self.mockDatabase, 8);
136
137 // Break the key hierarchy
138 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
139 [self saveTLKMaterialToKeychain:self.keychainZoneID];
140
141 CKReference* newClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
142 self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey] = oldClassCKey;
143 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
144
145 [self.keychainView notifyZoneChange:nil];
146
147 // CKKS should then fix the pointers, but not update any keys
148 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 zoneID:self.keychainZoneID];
149 [self.keychainView notifyZoneChange:nil];
150 OCMVerifyAllWithDelay(self.mockDatabase, 8);
151
152 // And then upload the item as usual
153 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
154 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
155 [self addGenericPassword: @"data" account: @"account-delete-me-2"];
156 OCMVerifyAllWithDelay(self.mockDatabase, 8);
157
158 [self waitForCKModifications];
159 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
160 }
161
162 - (void)testRecoverFromWrongClassCCurrentKeyPointersOnNotificationFixRejected {
163 // The current key pointers in cloudkit should always point directly under the top TLK.
164
165 // Test starts with a good key hierarchy in our fake CloudKit, and the TLK already arrived.
166 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
167 [self saveTLKMaterialToKeychain:self.keychainZoneID];
168
169 CKRecordID* classCPointerRecordID = self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID;
170
171 CKReference* oldClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
172
173 // Spin up CKKS subsystem.
174 [self startCKKSSubsystem];
175
176 // Uploading works
177 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
178 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
179 [self addGenericPassword: @"data" account: @"account-delete-me"];
180 OCMVerifyAllWithDelay(self.mockDatabase, 8);
181
182 // Break the key hierarchy
183 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
184 [self saveTLKMaterialToKeychain:self.keychainZoneID];
185
186 CKReference* newClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
187 CKRecord* classCPointer = self.keychainZone.currentDatabase[classCPointerRecordID];
188
189 CKRecord* brokenClassCPointer = [classCPointer copy];
190 brokenClassCPointer[SecCKRecordParentKeyRefKey] = oldClassCKey;
191 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
192 [self.keychainZone addCKRecordToZone:brokenClassCPointer];
193
194 self.silentFetchesAllowed = false;
195 [self expectCKFetchAndRunBeforeFinished: ^{
196 // Directly after CKKS fetches, we should fix up the pointers to be right again
197 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = newClassCKey.recordID.recordName;
198 [self.keychainZone addToZone: classCPointer];
199 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
200 self.silentFetchesAllowed = true;
201 }];
202
203 // CKKS should try to fix the pointers, but be rejected (since someone else has already fixed them)
204 // It should not try again, because someone already fixed them
205 [self expectCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID];
206 [self.keychainView notifyZoneChange:nil];
207 OCMVerifyAllWithDelay(self.mockDatabase, 8);
208
209 // And then use the 'new' key as it should
210 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
211 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
212 [self addGenericPassword: @"data" account: @"account-delete-me-2"];
213 OCMVerifyAllWithDelay(self.mockDatabase, 8);
214
215 [self waitForCKModifications];
216 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
217 }
218
219 - (void)testRecoverFromWrongClassCCurrentKeyPointersOnRecordWrite {
220 // The current key pointers in cloudkit should always point directly under the top TLK.
221
222 // Test starts with a good but rolled key hierarchy in our fake CloudKit, and the TLK already arrived.
223 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
224 [self saveTLKMaterialToKeychain:self.keychainZoneID];
225
226 CKRecordID* classCPointerRecordID = self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID;
227 CKReference* oldClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
228
229 [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
230 [self saveTLKMaterialToKeychain:self.keychainZoneID];
231
232 // Spin up CKKS subsystem.
233 [self startCKKSSubsystem];
234
235 // Uploading works
236 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
237 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
238 [self addGenericPassword: @"data" account: @"account-delete-me"];
239 OCMVerifyAllWithDelay(self.mockDatabase, 8);
240
241 // Now, break the class C pointer, but don't tell CKKS
242 CKReference* newClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
243 CKRecord* classCPointer = self.keychainZone.currentDatabase[classCPointerRecordID];
244
245 CKRecord* brokenClassCPointer = [classCPointer copy];
246 brokenClassCPointer[SecCKRecordParentKeyRefKey] = oldClassCKey;
247 self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
248 [self.keychainZone addCKRecordToZone:brokenClassCPointer];
249
250 // CKKS should receive a key hierarchy error, since it's wrong in CloudKit
251 // It should then fix the pointers and retry the upload
252 [self expectCKReceiveSyncKeyHierarchyError:self.keychainZoneID];
253 [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 zoneID:self.keychainZoneID];
254
255 // And then use the 'new' key as it should
256 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
257 checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
258 [self addGenericPassword: @"data" account: @"account-delete-me-2"];
259 OCMVerifyAllWithDelay(self.mockDatabase, 8);
260
261 [self waitForCKModifications];
262 XCTAssertEqualObjects(newClassCKey, self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey], "CKKS should have fixed up the broken class C key reference");
263 }
264
265 @end
266
267 #endif // OCTAGON