]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSTests+ItemSyncChoice.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSTests+ItemSyncChoice.m
1 /*
2 * Copyright (c) 2020 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/categories/NSError+UsefulConstructors.h"
31 #import "keychain/ckks/CKKS.h"
32 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
33 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
34 #import "keychain/ckks/CloudKitCategories.h"
35 #import "keychain/ckks/tests/CKKSTests.h"
36 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
37 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
38 #import "keychain/ckks/tests/MockCloudKit.h"
39
40 @interface CloudKitKeychainSyncingItemSyncChoiceTests : CloudKitKeychainSyncingTestsBase
41 @property CKKSSOSSelfPeer* remotePeer1;
42 @end
43
44 @implementation CloudKitKeychainSyncingItemSyncChoiceTests
45
46 - (size_t)outgoingQueueSize:(CKKSKeychainView*)view {
47 __block size_t result = 0;
48
49 [view dispatchSyncWithReadOnlySQLTransaction:^{
50 NSError* zoneError = nil;
51 NSArray<CKKSOutgoingQueueEntry*>* entries = [CKKSOutgoingQueueEntry all:view.zoneID error:&zoneError];
52 XCTAssertNil(zoneError, "should be no error fetching all OQEs");
53
54 result = (size_t)entries.count;
55 }];
56 return result;
57 }
58
59 - (size_t)incomingQueueSize:(CKKSKeychainView*)view {
60 __block size_t result = 0;
61
62 [view dispatchSyncWithReadOnlySQLTransaction:^{
63 NSError* zoneError = nil;
64 NSArray<CKKSIncomingQueueEntry*>* entries = [CKKSIncomingQueueEntry all:view.zoneID error:&zoneError];
65 XCTAssertNil(zoneError, "should be no error fetching all IQEs");
66
67 result = (size_t)entries.count;
68 }];
69 return result;
70 }
71
72 - (void)setUp {
73 [super setUp];
74
75 self.remotePeer1 = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"remote-peer1"
76 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
77 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
78 viewList:self.managedViewList];
79 }
80
81 - (void)testAddItemToPausedView {
82 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
83
84 [self startCKKSSubsystem];
85 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should have arrived at ready");
86
87 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
88 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED]
89 policyIsFresh:NO];
90
91 [self addGenericPassword:@"data" account:@"account-delete-me"];
92 [self.keychainView waitForOperationsOfClass:[CKKSOutgoingQueueOperation class]];
93 XCTAssertEqual(1, [self outgoingQueueSize:self.keychainView], "There should be one pending item in the outgoing queue");
94
95 // and again
96 [self addGenericPassword:@"data" account:@"account-delete-me-2"];
97 [self.keychainView waitForOperationsOfClass:[CKKSOutgoingQueueOperation class]];
98 XCTAssertEqual(2, [self outgoingQueueSize:self.keychainView], "There should be two pending item in the outgoing queue");
99
100 // When syncing is enabled, these items should sync
101 [self expectCKModifyItemRecords:2 currentKeyPointerRecords:1 zoneID:self.keychainZoneID];
102
103 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
104 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_ENABLED]
105 policyIsFresh:NO];
106 OCMVerifyAllWithDelay(self.mockDatabase, 20);
107 }
108
109 - (void)testReceiveItemToPausedView {
110 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
111
112 [self startCKKSSubsystem];
113 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Key state should have arrived at ready");
114
115 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
116 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED]
117 policyIsFresh:NO];
118
119 [self findGenericPassword: @"account0" expecting:errSecItemNotFound];
120
121 [self.keychainZone addToZone:[self createFakeRecord:self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D00" withAccount:@"account0"]];
122 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
123 [self.keychainView waitForFetchAndIncomingQueueProcessing];
124 XCTAssertEqual(1, [self incomingQueueSize:self.keychainView], "There should be one pending item in the incoming queue");
125
126 [self.keychainZone addToZone:[self createFakeRecord:self.keychainZoneID recordName:@"7B598D31-F9C5-481E-0000-5A507ACB2D00" withAccount:@"account1"]];
127 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
128 [self.keychainView waitForFetchAndIncomingQueueProcessing];
129 XCTAssertEqual(2, [self incomingQueueSize:self.keychainView], "There should be two pending item in the incoming queue");
130
131 [self findGenericPassword:@"account0" expecting:errSecItemNotFound];
132 [self findGenericPassword:@"account1" expecting:errSecItemNotFound];
133
134 // When syncing is enabled, these items should sync
135 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
136 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_ENABLED]
137 policyIsFresh:NO];
138
139 [self.keychainView waitForOperationsOfClass:[CKKSIncomingQueueOperation class]];
140 [self findGenericPassword:@"account0" expecting:errSecSuccess];
141 [self findGenericPassword:@"account1" expecting:errSecSuccess];
142 }
143
144 - (void)testAcceptKeyHierarchyWhilePaused {
145 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
146 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED]
147 policyIsFresh:NO];
148
149 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
150 [self saveTLKMaterialToKeychain:self.keychainZoneID];
151 [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
152
153 [self startCKKSSubsystem];
154
155 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should have become ready");
156 }
157
158 - (void)testUploadSelfTLKShare {
159 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
160 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED]
161 policyIsFresh:NO];
162
163 // Test starts with no keys in CKKS database, but one in our fake CloudKit.
164 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
165
166 // Test also starts with the TLK shared to all trusted peers from peer1
167 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
168 [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
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 [self startCKKSSubsystem];
173 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should become ready");
174
175 OCMVerifyAllWithDelay(self.mockDatabase, 20);
176 }
177
178 - (void)testSendNewTLKSharesOnTrustSetAddition {
179 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
180 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED]
181 policyIsFresh:NO];
182
183 // step 1: add a new peer; we should share the TLK with them
184 // start with no trusted peers
185 [self.mockSOSAdapter.trustedPeers removeAllObjects];
186
187 [self startCKKSSubsystem];
188 [self performOctagonTLKUpload:self.ckksViews];
189
190 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
191 [self.mockSOSAdapter.trustedPeers addObject:self.remotePeer1];
192 [self.mockSOSAdapter sendTrustedPeerSetChangedUpdate];
193
194 OCMVerifyAllWithDelay(self.mockDatabase, 20);
195 [self waitForCKModifications];
196
197 // and just double-check that no syncing is occurring
198 [self addGenericPassword:@"data" account:@"account-delete-me"];
199 [self.keychainView waitForOperationsOfClass:[CKKSOutgoingQueueOperation class]];
200 XCTAssertEqual(1, [self outgoingQueueSize:self.keychainView], "There should be one pending item in the outgoing queue");
201 }
202
203 - (void)testAddAndNotifyOnSyncDuringPausedOperation {
204 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
205 [self startCKKSSubsystem];
206
207 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should have become ready");
208 OCMVerifyAllWithDelay(self.mockDatabase, 20);
209
210 [self.keychainView setCurrentSyncingPolicy:[self viewSortingPolicyForManagedViewListWithUserControllableViews:[NSSet setWithObject:self.keychainView.zoneName]
211 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED]
212 policyIsFresh:NO];
213
214 NSMutableDictionary* query = [@{
215 (id)kSecClass : (id)kSecClassGenericPassword,
216 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
217 (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
218 (id)kSecAttrAccount : @"testaccount",
219 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
220 (id)kSecAttrSyncViewHint : self.keychainView.zoneName,
221 (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
222 } mutableCopy];
223
224 XCTestExpectation* blockExpectation = [self expectationWithDescription: @"callback occurs"];
225
226 XCTAssertEqual(errSecSuccess, _SecItemAddAndNotifyOnSync((__bridge CFDictionaryRef) query, NULL, ^(bool didSync, CFErrorRef cferror) {
227 XCTAssertFalse(didSync, "Item did not sync");
228
229 NSError* error = (__bridge NSError*)cferror;
230 XCTAssertNotNil(error, "Error syncing item");
231 XCTAssertEqual(error.domain, CKKSErrorDomain, "Error domain was CKKSErrorDomain");
232 XCTAssertEqual(error.code, CKKSErrorViewIsPaused, "Error code is 'view is paused'");
233
234 [blockExpectation fulfill];
235 }), @"_SecItemAddAndNotifyOnSync succeeded");
236
237 OCMVerifyAllWithDelay(self.mockDatabase, 10);
238
239 [self waitForExpectationsWithTimeout:5.0 handler:nil];
240 }
241
242 @end
243
244 #endif // OCTAGON