]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSLoggerTests.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSLoggerTests.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 "CKKSTests.h"
27 #import "keychain/ckks/CKKSAnalytics.h"
28 #import <Security/SFSQLite.h>
29 #import <Foundation/Foundation.h>
30 #import <CloudKit/CloudKit.h>
31 #import <CloudKit/CloudKit_Private.h>
32 #import <XCTest/XCTest.h>
33 #import <OCMock/OCMock.h>
34
35 static NSString* tablePath = nil;
36
37 @interface SQLiteTests : XCTestCase
38 @end
39
40 @implementation SQLiteTests
41
42 + (void)setUp
43 {
44 [super setUp];
45
46 tablePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test_table.db"];
47 }
48
49 - (void)setUp
50 {
51 [super setUp];
52
53 [[NSFileManager defaultManager] removeItemAtPath:tablePath error:nil];
54 }
55
56 - (void)tearDown
57 {
58 [[NSFileManager defaultManager] removeItemAtPath:tablePath error:nil];
59
60 [super tearDown];
61 }
62
63 - (void)testCreateLoggingDatabase
64 {
65 NSString* schema = @"CREATE table test (test_column INTEGER);";
66 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
67 NSError* error = nil;
68 XCTAssertTrue([sqlTable openWithError:&error], @"failed to open sql database");
69 XCTAssertNil(error, "encountered error opening database: %@", error);
70 XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tablePath]);
71 [sqlTable close];
72 }
73
74 - (void)testInsertAndDelete
75 {
76 NSString* schema = @"CREATE table test (test_column INTEGER);";
77 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
78 NSError* error = nil;
79 XCTAssertTrue([sqlTable openWithError:&error], @"failed to open sql database");
80 XCTAssertNil(error, "encountered error opening database: %@", error);
81
82 [sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(1)}];
83 [sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(2)}];
84 [sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(3)}];
85 XCTAssertTrue([[sqlTable selectAllFrom:@"test" where:nil bindings:nil] count] == 3);
86
87 [sqlTable deleteFrom:@"test" where:@"test_column = ?" bindings:@[@3]];
88 XCTAssertTrue([[sqlTable selectAllFrom:@"test" where:nil bindings:nil] count] == 2);
89
90 [sqlTable executeSQL:@"delete from test"];
91 XCTAssertTrue([[sqlTable selectAllFrom:@"test" where:nil bindings:nil] count] == 0);
92 }
93
94 - (void)testDontCrashWhenThereAreNoWritePermissions
95 {
96 NSString* schema = @"CREATE table test (test_column INTEGER);";
97 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
98
99 NSError* error = nil;
100 XCTAssertNoThrow([sqlTable openWithError:&error], @"opening database threw an exception");
101 XCTAssertNil(error, "encountered error opening database: %@", error);
102 XCTAssertNoThrow([sqlTable close], @"closing database threw an exception");
103
104 NSDictionary* originalAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tablePath error:&error];
105 XCTAssertNil(error, @"encountered error getting database file attributes: %@", error);
106
107 [[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @(400), NSFileImmutable : @(YES)} ofItemAtPath:tablePath error:&error];
108 XCTAssertNil(error, @"encountered error setting database file attributes: %@", error);
109 XCTAssertNoThrow([sqlTable openWithError:&error]);
110 XCTAssertNil(error, @"encounterd error when opening file without permissions: %@", error);
111
112 XCTAssertFalse([sqlTable executeSQL:@"insert or replace into test (test_column) VALUES (1)"],
113 @"writing to read-only database succeeded");
114
115 [[NSFileManager defaultManager] setAttributes:originalAttributes ofItemAtPath:tablePath error:&error];
116 XCTAssertNil(error, @"encountered error setting database file attributes back to original attributes: %@", error);
117 }
118
119 - (void)testDontCrashFromInternalErrors
120 {
121 NSString* schema = @"CREATE table test (test_column INTEGER);";
122 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
123
124 NSError* error = nil;
125 XCTAssertTrue([sqlTable openWithError:&error], @"failed to open database");
126 XCTAssertNil(error, "encountered error opening database: %@", error);
127
128 // delete the table to create havoc
129 XCTAssertTrue([sqlTable executeSQL:@"drop table test;"], @"deleting test table should have worked");
130
131 XCTAssertNoThrow([sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(1)}], @"inserting into deleted table threw an exception");
132 }
133
134 @end
135
136 @interface CKKSAnalyticsTests : CloudKitKeychainSyncingTestsBase
137 @property id mockCKKSAnalytics;
138 @end
139
140 @implementation CKKSAnalyticsTests
141
142 - (void)setUp
143 {
144 self.mockCKKSAnalytics = OCMClassMock([CKKSAnalytics class]);
145 OCMStub([self.mockCKKSAnalytics databasePath]).andCall(self, @selector(databasePath));
146 [super setUp];
147 }
148
149 - (void)tearDown
150 {
151 [self.mockCKKSAnalytics stopMocking];
152 self.mockCKKSAnalytics = nil;
153 [super tearDown];
154 }
155
156 - (NSString*)databasePath
157 {
158 return [NSTemporaryDirectory() stringByAppendingPathComponent:@"test_ckks_analytics_v2.db"];
159 }
160
161 static void _XCTAssertTimeDiffWithInterval(CKKSAnalyticsTests* self, const char* filename, int line, NSDate* a, NSDate* b, int delta, NSString * _Nullable format, ...) NS_FORMAT_FUNCTION(7,8);
162
163 /*
164 * Check if [a,b] are within expected time difference.
165 * The actual acceptable range is (-1.0, d.0] -- to deal with rounding errors when stored in SQLite.
166 */
167
168 #define XCTAssertTimeDiffWithInterval(a, b, delta, ...) \
169 _XCTAssertTimeDiffWithInterval(self, __FILE__, __LINE__, a, b, delta, @"" __VA_ARGS__)
170
171 static void _XCTAssertTimeDiffWithInterval(CKKSAnalyticsTests* self, const char* filename, int line, NSDate* a, NSDate* b, int delta, NSString * _Nullable format, ...) {
172 NSTimeInterval interval = [b timeIntervalSinceDate:a];
173 if (interval <= -1.0 || interval > delta) {
174 NSString *comparison = [[NSString alloc] initWithFormat:@"time diff not expected (a=%@(%f), b=%@(%f), delta = %f) -- valid range (-1.0, %d.0]: ", a, [a timeIntervalSince1970], b, [b timeIntervalSince1970], interval, delta];
175 NSString *arg = [[NSString alloc] init];
176 if (format) {
177 va_list args;
178 va_start(args, format);
179 va_end(args);
180 arg = [[NSString alloc] initWithFormat: format arguments: args];
181 }
182
183 XCTIssue* issue = [[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure
184 compactDescription:[comparison stringByAppendingString: arg]
185 detailedDescription:nil
186 sourceCodeContext:[[XCTSourceCodeContext alloc] init]
187 associatedError:nil
188 attachments:@[]];
189 [self recordIssue:issue];
190 }
191 }
192
193 - (void)testLastSuccessfulXDate
194 {
195 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
196 [self startCKKSSubsystem];
197 CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
198 [self.keychainZone addToZone: ckr];
199
200 [self.injectedManager.zoneChangeFetcher notifyZoneChange:nil];
201
202 [[self.injectedManager.zoneChangeFetcher inflightFetch] waitUntilFinished];
203 CKKSResultOperation* op = [self.keychainView processIncomingQueue:false];
204 [[op completionHandlerDidRunCondition] wait:4 * NSEC_PER_SEC];
205
206 NSDate* nowDate = [NSDate date];
207
208 /*
209 * Check last sync date for class A
210 */
211 NSDate* syncADate = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassA zoneName:self.keychainView.zoneName];
212 XCTAssertNotNil(syncADate, "Failed to get a last successful A sync date");
213 XCTAssertTimeDiffWithInterval(syncADate, nowDate, 15, "Last sync A date should be recent");
214
215 /*
216 * Check last sync date for class C
217 */
218 NSDate *syncCDate = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassC zoneName:self.keychainView.zoneName];
219 XCTAssertNotNil(syncCDate, "Failed to get a last successful C sync date");
220 XCTAssertTimeDiffWithInterval(syncCDate, nowDate, 15, "Last sync C date should be recent");
221
222 /*
223 * Check last unlock date
224 */
225 NSDate* unlockDate = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastUnlock];
226 XCTAssertNotNil(unlockDate, "Failed to get a last unlock date");
227 XCTAssertTimeDiffWithInterval(unlockDate, nowDate, 15, "Last unlock date should be recent");
228
229 sleep(2); // wait to be a different second (+/- 1s)
230
231 self.aksLockState = true;
232 [self.lockStateTracker recheck];
233
234 NSDate* newUnlockDate = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastUnlock];
235 XCTAssertNotNil(newUnlockDate, "Failed to get a last unlock date");
236
237 XCTAssertTimeDiffWithInterval(newUnlockDate, unlockDate, 1, "unlock dates not the same (within one second)");
238
239 sleep(1); // wait to be a differnt second
240
241 self.aksLockState = false;
242 [self.lockStateTracker recheck];
243
244 sleep(1); // wait for the completion block to have time to fire
245
246 newUnlockDate = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastUnlock];
247 XCTAssertNotNil(newUnlockDate, "Failed to get a last unlock date");
248 XCTAssertNotEqualObjects(newUnlockDate, unlockDate, "unlock date the same");
249 }
250
251 - (void)testRaceToCreateLoggers
252 {
253 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
254 for (NSInteger i = 0; i < 5; i++) {
255 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
256 CKKSAnalytics* logger = [CKKSAnalytics logger];
257 [logger logSuccessForEvent:(CKKSAnalyticsFailableEvent*)@"test_event" zoneName:self.keychainView.zoneName];
258 dispatch_semaphore_signal(semaphore);
259 });
260 }
261
262 for (NSInteger i = 0; i < 5; i++) {
263 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
264 }
265 }
266
267 - (void)testUnderlayingError
268 {
269 NSDictionary *errorString = nil;
270 NSError *error = nil;
271
272 error = [NSError errorWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:@{
273 CKPartialErrorsByItemIDKey : @{
274 @"recordid" : [NSError errorWithDomain:CKErrorDomain code:1 userInfo:nil],
275 }
276 }];
277
278 errorString = [[CKKSAnalytics logger] errorChain:error depth:0];
279
280 XCTAssertEqualObjects(errorString[@"domain"], CKErrorDomain, "error domain");
281 XCTAssertEqual([errorString[@"code"] intValue], CKErrorPartialFailure, "error code");
282
283 XCTAssertEqualObjects(errorString[@"oneCloudKitPartialFailure"][@"domain"], CKErrorDomain, "error domain");
284 XCTAssertEqual([errorString[@"oneCloudKitPartialFailure"][@"code"] intValue], 1, "error code");
285
286 /* interal partial error leaks out of CK */
287
288 error = [NSError errorWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:@{
289 CKPartialErrorsByItemIDKey : @{
290 @"recordid1" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
291 @"recordid2" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
292 @"recordid3" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
293 @"recordid4" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
294 @"recordid5" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
295 @"recordid6" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
296 @"recordid7" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
297 @"recordid8" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
298 @"recordid9" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
299 @"recordid0" : [NSError errorWithDomain:CKErrorDomain code:1 userInfo:nil],
300 @"recordid10" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
301 @"recordid12" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
302 @"recordid13" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
303 @"recordid14" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
304 @"recordid15" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
305 @"recordid16" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
306 @"recordid17" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
307 @"recordid18" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
308 @"recordid19" : [NSError errorWithDomain:CKErrorDomain code:CKErrorBatchRequestFailed userInfo:nil],
309 }
310 }];
311
312 errorString = [[CKKSAnalytics logger] errorChain:error depth:0];
313
314 XCTAssertEqualObjects(errorString[@"domain"], CKErrorDomain, "error domain");
315 XCTAssertEqual([errorString[@"code"] intValue], CKErrorPartialFailure, "error code");
316
317 XCTAssertEqualObjects(errorString[@"oneCloudKitPartialFailure"][@"domain"], CKErrorDomain, "error domain");
318 XCTAssertEqualObjects(errorString[@"oneCloudKitPartialFailure"][@"code"], @1, "error code");
319
320
321
322
323 error = [NSError errorWithDomain:@"domain" code:1 userInfo:@{
324 NSUnderlyingErrorKey : [NSError errorWithDomain:CKErrorDomain code:1 userInfo:nil],
325 }];
326
327 errorString = [[CKKSAnalytics logger] errorChain:error depth:0];
328
329 XCTAssertEqualObjects(errorString[@"domain"], @"domain", "error domain");
330 XCTAssertEqual([errorString[@"code"] intValue], 1, "error code");
331
332 XCTAssertEqualObjects(errorString[@"child"][@"domain"], CKErrorDomain, "error domain");
333 XCTAssertEqual([errorString[@"child"][@"code"] intValue], 1, "error code");
334 }
335
336
337 @end
338
339 #endif