]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSAPSHandlingTests.m
9245595f0e230f5321e8d882b392c4a29f71bcec
[apple/security.git] / keychain / ckks / tests / CKKSAPSHandlingTests.m
1 /*
2 * Copyright (c) 2018 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 <SecurityFoundation/SFKey.h>
30 #import <SecurityFoundation/SFKey_Private.h>
31 #import <SecurityFoundation/SFDigestOperation.h>
32
33 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
34 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
35 #import "keychain/ckks/CKKS.h"
36 #import "keychain/ckks/CKKSAPSReceiver.h"
37 #import "keychain/ckks/CKKSKey.h"
38 #import "keychain/ckks/CKKSPeer.h"
39 #import "keychain/ckks/CKKSTLKShare.h"
40 #import "keychain/ckks/CKKSViewManager.h"
41 #import "keychain/ckks/CloudKitCategories.h"
42
43 #import "keychain/ckks/tests/MockCloudKit.h"
44 #import "keychain/ckks/tests/CKKSTests.h"
45 #import "keychain/ot/OTDefines.h"
46
47 #import "keychain/ckks/tests/CKKSAPSReceiverTests.h"
48
49 @interface CKKSAPSHandlingTests : CloudKitKeychainSyncingTestsBase
50 @property CKRecordZoneID* manateeZoneID;
51 @property CKKSKeychainView* manateeView;
52 @property FakeCKZone* manateeZone;
53 @property (readonly) ZoneKeys* manateeZoneKeys;
54 @end
55
56
57 @implementation CKKSAPSHandlingTests
58 // We really just want two views here
59 - (NSSet*)managedViewList {
60 return [NSSet setWithObjects:@"keychain", @"Manatee", nil];
61 }
62
63 - (void)setUp {
64 [super setUp];
65
66 // Wait for the ViewManager to be brought up
67 XCTAssertEqual(0, [self.injectedManager.completedSecCKKSInitialize wait:20*NSEC_PER_SEC], "No timeout waiting for SecCKKSInitialize");
68
69 self.manateeZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Manatee" ownerName:CKCurrentUserDefaultName];
70 [self.ckksZones addObject:self.manateeZoneID];
71 self.manateeZone = [[FakeCKZone alloc] initZone: self.manateeZoneID];
72 self.zones[self.manateeZoneID] = self.manateeZone;
73 self.manateeView = [[CKKSViewManager manager] findView:@"Manatee"];
74 [self.ckksViews addObject:self.manateeView];
75 XCTAssertNotNil(self.manateeView, "CKKSViewManager created the Manatee view");
76 }
77
78 - (void)tearDown {
79 // If the test didn't already do this, allow each zone to spin up
80 self.accountStatus = CKAccountStatusNoAccount;
81 [self startCKKSSubsystem];
82
83 [self.manateeView halt];
84 [self.manateeView waitUntilAllOperationsAreFinished];
85 self.manateeView = nil;
86
87 [super tearDown];
88 }
89
90 - (void)testSendPushMetricUponRequest {
91 for(CKRecordZoneID* zoneID in self.ckksZones) {
92 [self putFakeKeyHierarchyInCloudKit:zoneID];
93 [self saveTLKMaterialToKeychain:zoneID];
94 [self expectCKKSTLKSelfShareUpload:zoneID];
95 }
96
97 [self startCKKSSubsystem];
98
99 for(CKKSKeychainView* view in self.ckksViews) {
100 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should enter 'ready' for view %@", view);
101 }
102
103 OCMVerifyAllWithDelay(self.mockDatabase, 20);
104
105 CKRecord* keychainRecord = [self createFakeRecord:self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85" withAccount:@"keychain-view"];
106 [self.keychainZone addToZone: keychainRecord];
107
108 // Manatee gets one too!
109 CKRecord* manateeRecord = [self createFakeRecord:self.manateeZoneID recordName:@"7B598D31-F9C5-481E-98AC-000000000000" withAccount:@"manatee-view"];
110 [self.manateeZone addToZone:manateeRecord];
111
112 // Trigger a notification just for keychain zone. Both keychain and Manatee should process their incoming queues, and receive their items.
113 CKKSResultOperation* keychainProcessOp = [self.keychainView resultsOfNextProcessIncomingQueueOperation];
114 XCTAssertNotNil(keychainProcessOp, "Should have gotten a promise operation from Keychain");
115 CKKSResultOperation* manateeProcessOp = [self.manateeView resultsOfNextProcessIncomingQueueOperation];
116 XCTAssertNotNil(manateeProcessOp, "Should have gotten a promise operation from Manatee");
117
118 // But if something goes wrong, don't block the whole test. Only way to do that is to make more operations, since there's no guarantee that the process ops above will ever be added
119 // to a queue (and thus become 'finished')
120 CKKSResultOperation* keychainProcessTimeoutOp = [CKKSResultOperation named:@"keychain-timeout" withBlock:^{}];
121 [keychainProcessTimeoutOp timeout:20*NSEC_PER_SEC];
122 [keychainProcessTimeoutOp addSuccessDependency:keychainProcessOp];
123 [self.operationQueue addOperation:keychainProcessTimeoutOp];
124
125 CKKSResultOperation* manateeProcessTimeoutOp = [CKKSResultOperation named:@"manatee-timeout" withBlock:^{}];
126 [manateeProcessTimeoutOp timeout:20*NSEC_PER_SEC];
127 [manateeProcessTimeoutOp addSuccessDependency:manateeProcessOp];
128 [self.operationQueue addOperation:manateeProcessTimeoutOp];
129
130 APSIncomingMessage* apsMessage = [CKKSAPSReceiverTests messageForZoneID:self.keychainZoneID];
131 NSUUID* nsuuid = [NSUUID UUID];
132 uuid_t uuid = {0};
133 [nsuuid getUUIDBytes:(unsigned char*)&uuid];
134 NSData* uuidData = [NSData dataWithBytes:&uuid length:sizeof(uuid)];
135
136 apsMessage.tracingUUID = uuidData;
137 apsMessage.tracingEnabled = YES;
138
139 // Inject a message at the APS layer
140 // Because we can only make APS receivers once iCloud tells us the push environment after sign-in, we can't use our normal injection strategy, and fell back on global state.
141 CKKSAPSReceiver* apsReceiver = [CKKSAPSReceiver receiverForEnvironment:self.apsEnvironment
142 namedDelegatePort:SecCKKSAPSNamedPort
143 apsConnectionClass:[FakeAPSConnection class]];
144 XCTAssertNotNil(apsReceiver, "Should have gotten an APS receiver");
145
146 // Also, CKKS should handle this in one single fetch
147 self.silentFetchesAllowed = false;
148 [self expectCKFetch];
149
150 // Expect two metric pushes, one from receiving the push and one from after we finish the fetch
151 // AFAICT there's no way to introspect a metric object to ensure we did it right
152 OCMExpect([self.mockContainerExpectations submitEventMetric:[OCMArg any]]);
153 OCMExpect([self.mockContainerExpectations submitEventMetric:[OCMArg any]]);
154
155 // Launch!
156 [apsReceiver connection:nil didReceiveIncomingMessage:apsMessage];
157
158 OCMVerifyAllWithDelay(self.mockContainerExpectations, 16);
159
160 // Now, wait for both views to run their processing
161 [keychainProcessTimeoutOp waitUntilFinished];
162 XCTAssertNil(keychainProcessTimeoutOp.error, "Shouldn't have been any error processing incoming queue (keychain)");
163
164 [manateeProcessTimeoutOp waitUntilFinished];
165 XCTAssertNil(manateeProcessTimeoutOp.error, "Shouldn't have been any error processing incoming queue (manatee)");
166
167 [self findGenericPassword:@"keychain-view" expecting:errSecSuccess];
168 [self findGenericPassword:@"manatee-view" expecting:errSecSuccess];
169 }
170
171 - (void)testDoNotSendPushMetricWithoutRequest {
172 for(CKRecordZoneID* zoneID in self.ckksZones) {
173 [self putFakeKeyHierarchyInCloudKit:zoneID];
174 [self saveTLKMaterialToKeychain:zoneID];
175 [self expectCKKSTLKSelfShareUpload:zoneID];
176 }
177
178 [self startCKKSSubsystem];
179
180 for(CKKSKeychainView* view in self.ckksViews) {
181 XCTAssertEqual(0, [view.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "Key state should enter 'ready' for view %@", view);
182 }
183
184 OCMVerifyAllWithDelay(self.mockDatabase, 20);
185
186 CKRecord* keychainRecord = [self createFakeRecord:self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85" withAccount:@"keychain-view"];
187 [self.keychainZone addToZone: keychainRecord];
188
189 // Trigger a notification just for keychain zone. Both keychain and Manatee should process their incoming queues, and receive their items.
190 CKKSResultOperation* keychainProcessOp = [self.keychainView resultsOfNextProcessIncomingQueueOperation];
191 XCTAssertNotNil(keychainProcessOp, "Should have gotten a promise operation from Keychain");
192
193 // But if something goes wrong, don't block the whole test. Only way to do that is to make more operations, since there's no guarantee that the process ops above will ever be added
194 // to a queue (and thus become 'finished')
195 CKKSResultOperation* keychainProcessTimeoutOp = [CKKSResultOperation named:@"keychain-timeout" withBlock:^{}];
196 [keychainProcessTimeoutOp timeout:20*NSEC_PER_SEC];
197 [keychainProcessTimeoutOp addSuccessDependency:keychainProcessOp];
198 [self.operationQueue addOperation:keychainProcessTimeoutOp];
199
200 APSIncomingMessage* apsMessage = [CKKSAPSReceiverTests messageForZoneID:self.keychainZoneID];
201 NSUUID* nsuuid = [NSUUID UUID];
202 uuid_t uuid = {0};
203 [nsuuid getUUIDBytes:(unsigned char*)&uuid];
204 NSData* uuidData = [NSData dataWithBytes:&uuid length:sizeof(uuid)];
205
206 apsMessage.tracingUUID = uuidData;
207 apsMessage.tracingEnabled = NO;
208
209 // Inject a message at the APS layer
210 // Because we can only make APS receivers once iCloud tells us the push environment after sign-in, we can't use our normal injection strategy, and fell back on global state.
211 CKKSAPSReceiver* apsReceiver = [CKKSAPSReceiver receiverForEnvironment:self.apsEnvironment
212 namedDelegatePort:SecCKKSAPSNamedPort
213 apsConnectionClass:[FakeAPSConnection class]];
214 XCTAssertNotNil(apsReceiver, "Should have gotten an APS receiver");
215
216 // Also, CKKS should handle this in one single fetch
217 self.silentFetchesAllowed = false;
218 [self expectCKFetch];
219
220 // Any metric push is verboten
221 OCMReject([self.mockContainerExpectations submitEventMetric:[OCMArg any]]);
222
223 // Launch!
224 [apsReceiver connection:nil didReceiveIncomingMessage:apsMessage];
225 OCMVerifyAllWithDelay(self.mockContainerExpectations, 16);
226
227 // Now, wait for both views to run their processing
228 [keychainProcessTimeoutOp waitUntilFinished];
229 XCTAssertNil(keychainProcessTimeoutOp.error, "Shouldn't have been any error processing incoming queue (keychain)");
230
231 [self findGenericPassword:@"keychain-view" expecting:errSecSuccess];
232 }
233
234 @end
235
236 #endif