]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSSOSTests.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSSOSTests.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 <notify.h>
30
31 #include <Security/SecItemPriv.h>
32
33 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
34 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
35 #import "keychain/ckks/tests/CKKSTests+MultiZone.h"
36
37 #import "keychain/ckks/CKKS.h"
38 #import "keychain/ckks/CKKSKeychainView.h"
39 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
40 #import "keychain/ckks/CKKSItemEncrypter.h"
41 #import "keychain/ckks/CKKSKey.h"
42 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
43 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
44 #import "keychain/ckks/CKKSSynchronizeOperation.h"
45 #import "keychain/ckks/CKKSViewManager.h"
46 #import "keychain/ckks/CKKSZoneStateEntry.h"
47 #import "keychain/ckks/CKKSManifest.h"
48
49 #import "keychain/ckks/tests/MockCloudKit.h"
50
51 #pragma clang diagnostic push
52 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
53 #include <Security/SecureObjectSync/SOSCloudCircle.h>
54 #include "keychain/SecureObjectSync/SOSAccountPriv.h"
55 #include "keychain/SecureObjectSync/SOSAccount.h"
56 #include "keychain/SecureObjectSync/SOSInternal.h"
57 #include "keychain/SecureObjectSync/SOSFullPeerInfo.h"
58 #pragma clang diagnostic pop
59
60 #include <Security/SecKey.h>
61 #include <Security/SecKeyPriv.h>
62 #pragma clang diagnostic pop
63
64 @interface CloudKitKeychainSyncingSOSIntegrationTests : CloudKitKeychainSyncingMultiZoneTestsBase
65 @end
66
67 @implementation CloudKitKeychainSyncingSOSIntegrationTests
68
69 - (void)testFindManateePiggyTLKs {
70 [self saveFakeKeyHierarchyToLocalDatabase:self.manateeZoneID];
71 [self saveTLKMaterialToKeychain:self.manateeZoneID];
72
73 [self startCKKSSubsystem];
74
75 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
76
77 [self deleteTLKMaterialFromKeychain:self.manateeZoneID];
78
79 [self SOSPiggyBackAddToKeychain:piggyTLKs];
80
81 NSError* error = nil;
82 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
83 XCTAssertNil(error, "No error loading tlk from piggy contents");
84 }
85
86 - (void)testFindPiggyTLKs {
87 [self putFakeKeyHierachiesInCloudKit];
88 [self putFakeDeviceStatusesInCloudKit];
89 [self saveTLKsToKeychain];
90
91 [self startCKKSSubsystem];
92
93 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
94
95 [self deleteTLKMaterialsFromKeychain];
96
97 [self SOSPiggyBackAddToKeychain:piggyTLKs];
98
99 NSError* error = nil;
100 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
101 XCTAssertNil(error, "No error loading manatee tlk from piggy contents");
102
103 [self.engramZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
104 XCTAssertNil(error, "No error loading engram tlk from piggy contents");
105
106 [self.autoUnlockZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
107 XCTAssertNil(error, "No error loading AutoUnlock tlk from piggy contents");
108
109 [self.healthZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
110 XCTAssertNil(error, "No error loading Health tlk from piggy contents");
111
112 [self.applepayZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
113 XCTAssertNil(error, "No error loading ApplePay tlk from piggy contents");
114
115 [self.homeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
116 XCTAssertNil(error, "No error loading Home tlk from piggy contents");
117 }
118
119 -(void)testPiggybackingData{
120 [self putFakeKeyHierachiesInCloudKit];
121 [self saveTLKsToKeychain];
122
123 for(CKRecordZoneID* zoneID in self.ckksZones) {
124 [self expectCKKSTLKSelfShareUpload:zoneID];
125 }
126 [self startCKKSSubsystem];
127
128 [self waitForKeyHierarchyReadinesses];
129
130 OCMVerifyAllWithDelay(self.mockDatabase, 20);
131
132 /*
133 * Pull data from keychain and view manager
134 */
135
136 NSDictionary* piggydata = [self SOSPiggyBackCopyFromKeychain];
137 NSArray<NSData *>* icloudidentities = piggydata[@"idents"];
138 NSArray<NSDictionary *>* tlks = piggydata[@"tlk"];
139
140 XCTAssertEqual([tlks count], [[self.injectedManager viewList] count], "TLKs not same as views");
141
142 XCTAssertNotNil(tlks, "tlks not set");
143 XCTAssertNotEqual([tlks count], (NSUInteger)0, "0 tlks");
144 XCTAssertNotNil(icloudidentities, "idents not set");
145 XCTAssertNotEqual([icloudidentities count], (NSUInteger)0, "0 icloudidentities");
146
147 NSData *initial = SOSPiggyCreateInitialSyncData(icloudidentities, tlks);
148
149 XCTAssertNotNil(initial, "Initial not set");
150 XCTAssertNotEqual((int)[initial length], 0, "initial sync data is greater than 0");
151
152 /*
153 * Check that they make it accross
154 */
155
156 const uint8_t* der = [initial bytes];
157 const uint8_t *der_end = der + [initial length];
158
159 NSDictionary *result = SOSPiggyCopyInitialSyncData(&der, der_end);
160 XCTAssertNotNil(result, "Initial not set");
161 NSArray *copiedTLKs = result[@"tlks"];
162 XCTAssertNotNil(copiedTLKs, "tlks not set");
163 XCTAssertEqual([copiedTLKs count], 5u, "piggybacking should have gotten 5 TLKs across (but we have more than that elsewhere)");
164
165 NSArray *copiediCloudidentities = result[@"idents"];
166 XCTAssertNotNil(copiediCloudidentities, "idents not set");
167 XCTAssertEqual([copiediCloudidentities count], [icloudidentities count], "ident count not same");
168 }
169
170
171 - (void)testPiggybackingTLKRequest {
172 [self putFakeKeyHierachiesInCloudKit];
173 [self saveTLKsToKeychain];
174
175 for(CKRecordZoneID* zoneID in self.ckksZones) {
176 [self expectCKKSTLKSelfShareUpload:zoneID];
177 }
178 [self startCKKSSubsystem];
179 [self waitForKeyHierarchyReadinesses];
180 OCMVerifyAllWithDelay(self.mockDatabase, 20);
181
182 // The "tlk request" piggybacking session calls SOSAccountCopyInitialSyncData
183 __block CFErrorRef cferror = NULL;
184 NSData* piggybackingData = (NSData*) CFBridgingRelease(SOSAccountCopyInitialSyncData(nil, kSOSInitialSyncFlagTLKsRequestOnly, &cferror));
185
186 XCTAssertEqual(cferror, NULL, "Should have no error fetching only the TLKs");
187 XCTAssertNotNil(piggybackingData, "Should have received some sync data");
188
189 const uint8_t* der = [piggybackingData bytes];
190 const uint8_t *der_end = der + [piggybackingData length];
191
192 NSDictionary *result = SOSPiggyCopyInitialSyncData(&der, der_end);
193 XCTAssertNotNil(result, "Should be able to parse the piggybacking data");
194
195 NSArray *copiedTLKs = result[@"tlks"];
196 XCTAssertNotNil(copiedTLKs, "should have some tlks");
197 XCTAssertEqual([copiedTLKs count], 1u, "piggybacking should have gotten 1 TLK");
198 XCTAssertEqualObjects(copiedTLKs[0][@"srvr"], @"Passwords", "should have the passwords TLK only");
199 NSData* keyData = copiedTLKs[0][@"v_Data"];
200 XCTAssertNotNil(keyData, "Should have some key material");
201 XCTAssertEqual([keyData length], 64, "Key material should be 64 bytes");
202
203 NSArray *copiediCloudidentities = result[@"idents"];
204 XCTAssertNotNil(copiediCloudidentities, "idents not set");
205 XCTAssertEqual([copiediCloudidentities count], 0, "Should have no icloud identities");
206 }
207
208 -(void)testVerifyTLKSorting {
209 char key[32*2] = {0};
210 NSArray<NSDictionary *> *tlks = @[
211 @{
212 @"acct" : @"11111111",
213 @"srvr" : @"Manatee",
214 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
215 @"auth" : @YES,
216 },
217 @{
218 @"acct" : @"55555555",
219 @"srvr" : @"Health",
220 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
221 },
222 @{
223 @"acct" : @"22222222",
224 @"srvr" : @"Engram",
225 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
226 @"auth" : @YES,
227 },
228 @{
229 @"acct" : @"44444444",
230 @"srvr" : @"Manatee",
231 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
232 },
233 @{
234 @"acct" : @"33333333",
235 @"srvr" : @"Health",
236 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
237 @"auth" : @YES,
238 },
239 @{
240 @"acct" : @"66666666",
241 @"srvr" : @"Home",
242 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
243 @"auth" : @YES,
244 },
245 ];
246
247 NSArray<NSDictionary *>* sortedTLKs = SOSAccountSortTLKS(tlks);
248 XCTAssertNotNil(sortedTLKs, "sortedTLKs not set");
249
250 // Home gets sorted into the middle, as the other Health and Manatee TLKs aren't 'authoritative'
251 NSArray<NSString *> *expectedOrder = @[ @"11111111", @"22222222", @"33333333", @"66666666", @"44444444", @"55555555"];
252 [sortedTLKs enumerateObjectsUsingBlock:^(NSDictionary *tlk, NSUInteger idx, BOOL * _Nonnull stop) {
253 NSString *uuid = tlk[@"acct"];
254 XCTAssertEqualObjects(uuid, expectedOrder[idx], "wrong order");
255 }];
256 }
257
258
259 - (void)testAcceptExistingPiggyKeyHierarchy {
260 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
261 // Test also begins with the TLK having arrived in the local keychain (via SOS)
262 [self putFakeKeyHierachiesInCloudKit];
263 [self saveTLKsToKeychain];
264 NSDictionary* piggyTLKS = [self SOSPiggyBackCopyFromKeychain];
265 [self SOSPiggyBackAddToKeychain:piggyTLKS];
266 [self deleteTLKMaterialsFromKeychain];
267
268 // The CKKS subsystem should write a TLK Share for each view
269 for(CKRecordZoneID* zoneID in self.ckksZones) {
270 [self expectCKKSTLKSelfShareUpload:zoneID];
271 }
272
273 // Spin up CKKS subsystem.
274 [self startCKKSSubsystem];
275
276 [self.manateeView waitForKeyHierarchyReadiness];
277
278 OCMVerifyAllWithDelay(self.mockDatabase, 20);
279
280 // Verify that there are three local keys, and three local current key records
281 __weak __typeof(self) weakSelf = self;
282 [self.manateeView dispatchSyncWithReadOnlySQLTransaction:^{
283 __strong __typeof(weakSelf) strongSelf = weakSelf;
284 XCTAssertNotNil(strongSelf, "self exists");
285
286 NSError* error = nil;
287
288 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.manateeZoneID error:&error];
289 XCTAssertNil(error, "no error fetching keys");
290 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
291
292 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:strongSelf.manateeZoneID error:&error];
293 XCTAssertNil(error, "no error fetching current keys");
294 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
295
296 // Ensure that the manatee syncable TLK is created from a piggy
297 NSDictionary* query = @{
298 (id)kSecClass : (id)kSecClassInternetPassword,
299 (id)kSecUseDataProtectionKeychain : @YES,
300 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
301 (id)kSecAttrDescription: SecCKKSKeyClassTLK,
302 (id)kSecAttrAccount: strongSelf.manateeZoneKeys.tlk.uuid,
303 (id)kSecAttrServer: strongSelf.manateeZoneID.zoneName,
304 (id)kSecAttrSynchronizable: @YES,
305 (id)kSecReturnAttributes: @YES,
306 (id)kSecReturnData: @YES,
307 };
308 CFTypeRef result = nil;
309 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), "Found a syncable TLK");
310 XCTAssertNotNil((__bridge id) result, "Received a result from SecItemCopyMatching");
311 CFReleaseNull(result);
312 }];
313 }
314
315 -(void)testAcceptExistingAndUsePiggyKeyHierarchy {
316 // Test starts with nothing in database, but one in our fake CloudKit.
317 [self putFakeKeyHierachiesInCloudKit];
318 [self putFakeDeviceStatusesInCloudKit];
319 [self saveTLKsToKeychain];
320 NSDictionary* piggyData = [self SOSPiggyBackCopyFromKeychain];
321 [self deleteTLKMaterialsFromKeychain];
322
323 // Spin up CKKS subsystem.
324 [self startCKKSSubsystem];
325
326 // The CKKS subsystem should not try to write anything to the CloudKit database.
327 XCTAssertEqual(0, [self.manateeView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "CKKS entered waitfortlk");
328
329 OCMVerifyAllWithDelay(self.mockDatabase, 20);
330
331 // Now, save the TLKs to the keychain (to simulate them coming in later via piggybacking).
332 for(CKRecordZoneID* zoneID in self.ckksZones) {
333 [self expectCKKSTLKSelfShareUpload:zoneID];
334 }
335
336 [self SOSPiggyBackAddToKeychain:piggyData];
337 [self waitForKeyHierarchyReadinesses];
338
339 // We expect a single record to be uploaded for each key class
340 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassCBlock:self.manateeZoneID message:@"Object was encrypted under class C key in hierarchy"]];
341 [self addGenericPassword: @"data" account: @"account-delete-me-manatee" viewHint:(id)kSecAttrViewHintManatee];
342
343 OCMVerifyAllWithDelay(self.mockDatabase, 20);
344
345 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassABlock:self.manateeZoneID message:@"Object was encrypted under class A key in hierarchy"]];
346
347 [self addGenericPassword:@"asdf"
348 account:@"account-class-A"
349 viewHint:(id)kSecAttrViewHintManatee
350 access:(id)kSecAttrAccessibleWhenUnlocked
351 expecting:errSecSuccess
352 message:@"Adding class A item"];
353 OCMVerifyAllWithDelay(self.mockDatabase, 20);
354 }
355 @end
356
357 #endif // OCTAGON
358