2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #import <CloudKit/CloudKit.h>
27 #import <XCTest/XCTest.h>
28 #import <OCMock/OCMock.h>
31 #include <Security/SecItemPriv.h>
33 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
34 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
35 #import "keychain/ckks/tests/CKKSTests+MultiZone.h"
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"
49 #import "keychain/ckks/tests/MockCloudKit.h"
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
60 #include <Security/SecKey.h>
61 #include <Security/SecKeyPriv.h>
62 #pragma clang diagnostic pop
64 @interface CloudKitKeychainSyncingSOSIntegrationTests : CloudKitKeychainSyncingMultiZoneTestsBase
67 @implementation CloudKitKeychainSyncingSOSIntegrationTests
69 - (void)testFindManateePiggyTLKs {
70 [self saveFakeKeyHierarchyToLocalDatabase:self.manateeZoneID];
71 [self saveTLKMaterialToKeychain:self.manateeZoneID];
73 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
75 [self deleteTLKMaterialFromKeychain:self.manateeZoneID];
77 [self SOSPiggyBackAddToKeychain:piggyTLKs];
80 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
81 XCTAssertNil(error, "No error loading tlk from piggy contents");
84 - (void)testFindPiggyTLKs {
85 [self putFakeKeyHierachiesInCloudKit];
86 [self putFakeDeviceStatusesInCloudKit];
87 [self saveTLKsToKeychain];
89 NSDictionary* piggyTLKs = [self SOSPiggyBackCopyFromKeychain];
91 [self deleteTLKMaterialsFromKeychain];
93 [self SOSPiggyBackAddToKeychain:piggyTLKs];
96 [self.manateeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
97 XCTAssertNil(error, "No error loading manatee tlk from piggy contents");
99 [self.engramZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
100 XCTAssertNil(error, "No error loading engram tlk from piggy contents");
102 [self.autoUnlockZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
103 XCTAssertNil(error, "No error loading AutoUnlock tlk from piggy contents");
105 [self.healthZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
106 XCTAssertNil(error, "No error loading Health tlk from piggy contents");
108 [self.applepayZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
109 XCTAssertNil(error, "No error loading ApplePay tlk from piggy contents");
111 [self.homeZoneKeys.tlk loadKeyMaterialFromKeychain:&error];
112 XCTAssertNil(error, "No error loading Home tlk from piggy contents");
115 -(NSString*)fileForStorage
117 static dispatch_once_t onceToken;
118 static NSString *tempPath = NULL;
119 dispatch_once(&onceToken, ^{
120 tempPath = [[[[NSFileManager defaultManager] temporaryDirectory] URLByAppendingPathComponent:@"PiggyPacket"] path];
126 -(void)testPiggybackingData{
127 [self putFakeKeyHierachiesInCloudKit];
128 [self saveTLKsToKeychain];
130 for(CKRecordZoneID* zoneID in self.ckksZones) {
131 [self expectCKKSTLKSelfShareUpload:zoneID];
133 [self startCKKSSubsystem];
135 [self waitForKeyHierarchyReadinesses];
137 OCMVerifyAllWithDelay(self.mockDatabase, 20);
140 * Pull data from keychain and view manager
143 NSDictionary* piggydata = [self SOSPiggyBackCopyFromKeychain];
144 NSArray<NSData *>* icloudidentities = piggydata[@"idents"];
145 NSArray<NSDictionary *>* tlks = piggydata[@"tlk"];
147 XCTAssertEqual([tlks count], [[self.injectedManager viewList] count], "TLKs not same as views");
149 XCTAssertNotNil(tlks, "tlks not set");
150 XCTAssertNotEqual([tlks count], (NSUInteger)0, "0 tlks");
151 XCTAssertNotNil(icloudidentities, "idents not set");
152 XCTAssertNotEqual([icloudidentities count], (NSUInteger)0, "0 icloudidentities");
154 NSData *initial = SOSPiggyCreateInitialSyncData(icloudidentities, tlks);
156 XCTAssertNotNil(initial, "Initial not set");
157 BOOL writeStatus = [initial writeToFile:[self fileForStorage] options:NSDataWritingAtomic error: nil];
158 XCTAssertTrue(writeStatus, "had trouble writing to disk");
159 XCTAssertNotEqual((int)[initial length], 0, "initial sync data is greater than 0");
162 * Check that they make it accross
165 const uint8_t* der = [initial bytes];
166 const uint8_t *der_end = der + [initial length];
168 NSDictionary *result = SOSPiggyCopyInitialSyncData(&der, der_end);
169 XCTAssertNotNil(result, "Initial not set");
170 NSArray *copiedTLKs = result[@"tlks"];
171 XCTAssertNotNil(copiedTLKs, "tlks not set");
172 XCTAssertEqual([copiedTLKs count], 5u, "piggybacking should have gotten 5 TLKs across (but we have more than that elsewhere)");
174 NSArray *copiediCloudidentities = result[@"idents"];
175 XCTAssertNotNil(copiediCloudidentities, "idents not set");
176 XCTAssertEqual([copiediCloudidentities count], [icloudidentities count], "ident count not same");
179 -(void)testVerifyTLKSorting {
180 char key[32*2] = {0};
181 NSArray<NSDictionary *> *tlks = @[
183 @"acct" : @"11111111",
184 @"srvr" : @"Manatee",
185 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
189 @"acct" : @"55555555",
191 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
194 @"acct" : @"22222222",
196 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
200 @"acct" : @"44444444",
201 @"srvr" : @"Manatee",
202 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
205 @"acct" : @"33333333",
207 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
211 @"acct" : @"66666666",
213 @"v_Data" : [NSData dataWithBytes:key length:sizeof(key)],
218 NSArray<NSDictionary *>* sortedTLKs = SOSAccountSortTLKS(tlks);
219 XCTAssertNotNil(sortedTLKs, "sortedTLKs not set");
221 // Home gets sorted into the middle, as the other Health and Manatee TLKs aren't 'authoritative'
222 NSArray<NSString *> *expectedOrder = @[ @"11111111", @"22222222", @"33333333", @"66666666", @"44444444", @"55555555"];
223 [sortedTLKs enumerateObjectsUsingBlock:^(NSDictionary *tlk, NSUInteger idx, BOOL * _Nonnull stop) {
224 NSString *uuid = tlk[@"acct"];
225 XCTAssertEqualObjects(uuid, expectedOrder[idx], "wrong order");
230 - (void)testAcceptExistingPiggyKeyHierarchy {
231 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
232 // Test also begins with the TLK having arrived in the local keychain (via SOS)
233 [self putFakeKeyHierachiesInCloudKit];
234 [self saveTLKsToKeychain];
235 NSDictionary* piggyTLKS = [self SOSPiggyBackCopyFromKeychain];
236 [self SOSPiggyBackAddToKeychain:piggyTLKS];
237 [self deleteTLKMaterialsFromKeychain];
239 // The CKKS subsystem should write a TLK Share for each view
240 for(CKRecordZoneID* zoneID in self.ckksZones) {
241 [self expectCKKSTLKSelfShareUpload:zoneID];
244 // Spin up CKKS subsystem.
245 [self startCKKSSubsystem];
247 [self.manateeView waitForKeyHierarchyReadiness];
249 OCMVerifyAllWithDelay(self.mockDatabase, 20);
251 // Verify that there are three local keys, and three local current key records
252 __weak __typeof(self) weakSelf = self;
253 [self.manateeView dispatchSync: ^bool{
254 __strong __typeof(weakSelf) strongSelf = weakSelf;
255 XCTAssertNotNil(strongSelf, "self exists");
257 NSError* error = nil;
259 NSArray<CKKSKey*>* keys = [CKKSKey localKeys:strongSelf.manateeZoneID error:&error];
260 XCTAssertNil(error, "no error fetching keys");
261 XCTAssertEqual(keys.count, 3u, "Three keys in local database");
263 NSArray<CKKSCurrentKeyPointer*>* currentkeys = [CKKSCurrentKeyPointer all:strongSelf.manateeZoneID error:&error];
264 XCTAssertNil(error, "no error fetching current keys");
265 XCTAssertEqual(currentkeys.count, 3u, "Three current key pointers in local database");
267 // Ensure that the manatee syncable TLK is created from a piggy
268 NSDictionary* query = @{
269 (id)kSecClass : (id)kSecClassInternetPassword,
270 (id)kSecUseDataProtectionKeychain : @YES,
271 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
272 (id)kSecAttrDescription: SecCKKSKeyClassTLK,
273 (id)kSecAttrAccount: strongSelf.manateeZoneKeys.tlk.uuid,
274 (id)kSecAttrServer: strongSelf.manateeZoneID.zoneName,
275 (id)kSecAttrSynchronizable: @YES,
276 (id)kSecReturnAttributes: @YES,
277 (id)kSecReturnData: @YES,
279 CFTypeRef result = nil;
280 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), "Found a syncable TLK");
281 XCTAssertNotNil((__bridge id) result, "Received a result from SecItemCopyMatching");
282 CFReleaseNull(result);
288 -(void)testAcceptExistingAndUsePiggyKeyHierarchy {
289 // Test starts with nothing in database, but one in our fake CloudKit.
290 [self putFakeKeyHierachiesInCloudKit];
291 [self putFakeDeviceStatusesInCloudKit];
292 [self saveTLKsToKeychain];
293 NSDictionary* piggyData = [self SOSPiggyBackCopyFromKeychain];
294 [self deleteTLKMaterialsFromKeychain];
296 // Spin up CKKS subsystem.
297 [self startCKKSSubsystem];
299 // The CKKS subsystem should not try to write anything to the CloudKit database.
300 XCTAssertEqual(0, [self.manateeView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], "CKKS entered waitfortlk");
302 OCMVerifyAllWithDelay(self.mockDatabase, 20);
304 // Now, save the TLKs to the keychain (to simulate them coming in later via piggybacking).
305 for(CKRecordZoneID* zoneID in self.ckksZones) {
306 [self expectCKKSTLKSelfShareUpload:zoneID];
309 [self SOSPiggyBackAddToKeychain:piggyData];
310 [self waitForKeyHierarchyReadinesses];
312 // We expect a single record to be uploaded for each key class
313 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassCBlock:self.manateeZoneID message:@"Object was encrypted under class C key in hierarchy"]];
314 [self addGenericPassword: @"data" account: @"account-delete-me-manatee" viewHint:(id)kSecAttrViewHintManatee];
316 OCMVerifyAllWithDelay(self.mockDatabase, 20);
318 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.manateeZoneID checkItem: [self checkClassABlock:self.manateeZoneID message:@"Object was encrypted under class A key in hierarchy"]];
320 [self addGenericPassword:@"asdf"
321 account:@"account-class-A"
322 viewHint:(id)kSecAttrViewHintManatee
323 access:(id)kSecAttrAccessibleWhenUnlocked
324 expecting:errSecSuccess
325 message:@"Adding class A item"];
326 OCMVerifyAllWithDelay(self.mockDatabase, 20);