2 * Copyright (c) 2016 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 <XCTest/XCTest.h>
27 #import <Security/Security.h>
28 #import <Security/SecItemPriv.h>
29 #import "CloudKitMockXCTest.h"
31 #import "keychain/ckks/CKKS.h"
32 #import "keychain/ckks/CKKSKey.h"
33 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
34 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
35 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
36 #import "keychain/ckks/CKKSZoneStateEntry.h"
37 #import "keychain/ckks/CKKSDeviceStateEntry.h"
38 #import "keychain/ckks/CKKSRateLimiter.h"
40 #include "keychain/securityd/SecItemServer.h"
42 @interface CloudKitKeychainSQLTests : CloudKitMockXCTest
45 @implementation CloudKitKeychainSQLTests
58 SecCKKSResetSyncing();
61 - (void)addTestZoneEntries {
62 CKKSOutgoingQueueEntry* one = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:
63 [[CKKSItem alloc] initWithUUID:[[NSUUID UUID] UUIDString]
64 parentKeyUUID:[[NSUUID UUID] UUIDString]
65 zoneID:self.testZoneID
66 encItem:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
67 wrappedkey:[[CKKSWrappedAESSIVKey alloc]initWithBase64: @"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="]
70 action:SecCKKSActionAdd
71 state:SecCKKSStateError
76 CKKSOutgoingQueueEntry* two = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:
77 [[CKKSItem alloc] initWithUUID:[[NSUUID UUID] UUIDString]
78 parentKeyUUID:[[NSUUID UUID] UUIDString]
79 zoneID:self.testZoneID
80 encItem:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
81 wrappedkey:[[CKKSWrappedAESSIVKey alloc]initWithBase64: @"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="]
84 action:SecCKKSActionAdd
89 CKKSOutgoingQueueEntry* three = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:
90 [[CKKSItem alloc] initWithUUID:[[NSUUID UUID] UUIDString]
91 parentKeyUUID:[[NSUUID UUID] UUIDString]
92 zoneID:self.testZoneID
93 encItem:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
94 wrappedkey:[[CKKSWrappedAESSIVKey alloc]initWithBase64: @"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="]
97 action:SecCKKSActionModify
98 state:SecCKKSStateError
100 accessGroup:@"nope"];
102 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
103 NSError* error = nil;
104 [one saveToDatabase:&error];
105 [two saveToDatabase: &error];
106 [three saveToDatabase: &error];
107 XCTAssertNil(error, "no error saving ZoneStateEntries to database");
108 return CKKSDatabaseTransactionCommit;
112 - (void)testCKKSOutgoingQueueEntry {
113 NSString* testUUID = @"157A3171-0677-451B-9EAE-0DDC4D4315B0";
114 NSUUID* testKeyUUID = [[NSUUID alloc] init];
117 __block CFErrorRef error = NULL;
119 CKKSOutgoingQueueEntry* shouldFail = [CKKSOutgoingQueueEntry fromDatabase:testUUID state:SecCKKSStateInFlight zoneID:self.testZoneID error: &nserror];
120 XCTAssertNil(shouldFail, "Can't find a nonexisting object");
121 XCTAssertNotNil(nserror, "NSError exists when things break");
123 __weak __typeof(self) weakSelf = self;
124 kc_with_dbt(true, &error, ^bool (SecDbConnectionRef dbconn) {
125 __strong __typeof(weakSelf) strongSelf = weakSelf;
126 XCTAssertNotNil(strongSelf, "called while self still exists");
128 NSString * sql = @"insert INTO outgoingqueue (UUID, parentKeyUUID, ckzone, action, state, accessgroup, gencount, encitem, wrappedkey, encver) VALUES (?,?,?,?,?,?,?,?,?,?);";
129 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &error, ^void (sqlite3_stmt *stmt) {
130 SecDbBindText(stmt, 1, [testUUID UTF8String], strlen([testUUID UTF8String]), NULL, &error);
131 SecDbBindText(stmt, 2, [[testKeyUUID UUIDString] UTF8String], strlen([[testKeyUUID UUIDString] UTF8String]), NULL, &error);
132 SecDbBindObject(stmt, 3, (__bridge CFStringRef) weakSelf.testZoneID.zoneName, &error);
133 SecDbBindText(stmt, 4, "newitem", strlen("newitem"), NULL, &error);
134 SecDbBindText(stmt, 5, "unprocessed", strlen("unprocessed"), NULL, &error);
135 SecDbBindText(stmt, 6, "com.apple.access", strlen("com.apple.access"), NULL, &error);
136 SecDbBindText(stmt, 7, "0", strlen("0"), NULL, &error);
137 SecDbBindText(stmt, 8, "bm9uc2Vuc2UK", strlen("bm9uc2Vuc2UK"), NULL, &error);
138 SecDbBindObject(stmt, 9, CFSTR("KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="), &error);
139 SecDbBindText(stmt, 10, "0", strlen("0"), NULL, &error);
141 SecDbStep(dbconn, stmt, &error, ^(bool *stop) {
142 // don't do anything, I guess?
145 XCTAssertNil((__bridge NSError*)error, @"no error occurred while adding row to database");
147 CFReleaseNull(error);
149 XCTAssertNil((__bridge NSError*)error, @"no error occurred preparing sql");
151 CFReleaseNull(error);
155 // Create another oqe with different values
156 CKKSItem* baseitem = [[CKKSItem alloc] initWithUUID: [[NSUUID UUID] UUIDString]
157 parentKeyUUID:[[NSUUID UUID] UUIDString]
158 zoneID:self.testZoneID
159 encItem:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
160 wrappedkey:[[CKKSWrappedAESSIVKey alloc]initWithBase64: @"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="]
163 CKKSOutgoingQueueEntry* other = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:baseitem
164 action:SecCKKSActionAdd
165 state:SecCKKSStateError
166 waitUntil:[NSDate date]
167 accessGroup:@"nope"];
168 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
169 NSError* saveError = nil;
170 [other saveToDatabase:&saveError];
171 XCTAssertNil(saveError, "no error occurred saving to database");
172 return CKKSDatabaseTransactionCommit;
175 CKKSOutgoingQueueEntry * oqe = [CKKSOutgoingQueueEntry fromDatabase:testUUID state:@"unprocessed" zoneID:self.testZoneID error: &nserror];
176 XCTAssertNil(nserror, "no error occurred creating from database");
178 XCTAssertNotNil(oqe, "load outgoing queue entry from database");
179 XCTAssertEqualObjects(oqe.state, @"unprocessed", "state matches what was in the DB");
181 oqe.item.parentKeyUUID = @"not a parent key either";
182 oqe.action = @"null";
183 oqe.state = @"savedtocloud";
184 oqe.accessgroup = @"com.evil.access";
185 oqe.item.generationCount = (NSInteger) 1;
186 oqe.item.base64encitem = @"bW9yZW5vbnNlbnNlCg==";
189 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
190 NSError* saveError = nil;
191 [oqe saveToDatabase:&saveError];
192 XCTAssertNil(saveError, "no error occurred saving to database");
193 return CKKSDatabaseTransactionCommit;
196 CKKSOutgoingQueueEntry * oqe2 = [CKKSOutgoingQueueEntry fromDatabase:testUUID state:@"savedtocloud" zoneID:self.testZoneID error: &nserror];
197 XCTAssertNil(nserror, "no error occurred");
199 XCTAssertEqualObjects(oqe2.item.parentKeyUUID, @"not a parent key either", @"parent key uuid persisted through db save and load");
200 XCTAssertEqualObjects(oqe2.item.zoneID , self.testZoneID , @"zone id persisted through db save and load");
201 XCTAssertEqualObjects(oqe2.action , @"null" , @"action persisted through db save and load");
202 XCTAssertEqualObjects(oqe2.state , @"savedtocloud" , @"state persisted through db save and load");
203 XCTAssertEqual( oqe2.waitUntil , nil , @"no date when none given");
204 XCTAssertEqualObjects(oqe2.accessgroup , @"com.evil.access" , @"accessgroup persisted through db save and load");
205 XCTAssertEqual( oqe2.item.generationCount, (NSUInteger) 1 , @"generationCount persisted through db save and load");
206 XCTAssertEqualObjects(oqe2.item.base64encitem, @"bW9yZW5vbnNlbnNlCg==" , @"encitem persisted through db save and load");
207 XCTAssertEqual( oqe2.item.encver, 1 , @"encver persisted through db save and load");
208 XCTAssertEqualObjects([oqe2.item.wrappedkey base64WrappedKey], @"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI=",
209 @"wrapped key persisted through db save and load");
211 // Test 'all' methods
212 NSArray<CKKSOutgoingQueueEntry*>* oqes = [CKKSOutgoingQueueEntry all:&nserror];
213 XCTAssertNil(nserror, "no error occurred");
214 XCTAssertNotNil(oqes, "receive oqes from database");
215 XCTAssert([oqes count] == 2, "received 2 oqes from all");
217 NSArray<CKKSOutgoingQueueEntry*>* oqeswhere = [CKKSOutgoingQueueEntry allWhere: @{@"state": @"savedtocloud"} error:&nserror];
218 XCTAssertNil(nserror, "no error occurred");
219 XCTAssertNotNil(oqeswhere, "receive oqes from database");
220 XCTAssert([oqeswhere count] == 1, "received 1 oqe from allWhere");
226 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
227 NSError* deleteError = nil;
228 [oqe2 deleteFromDatabase:&deleteError];
229 XCTAssertNil(deleteError, "no error occurred deleting existing item");
230 return CKKSDatabaseTransactionCommit;
233 oqe2 = [CKKSOutgoingQueueEntry fromDatabase:testUUID state:@"savedtocloud" zoneID:self.testZoneID error: &nserror];
234 XCTAssertNil(oqe2, "Can't find a nonexisting object");
235 XCTAssertNotNil(nserror, "NSError exists when things break");
237 // Test loading other
239 CKKSOutgoingQueueEntry* other2 = [CKKSOutgoingQueueEntry fromDatabase: other.item.uuid state:SecCKKSStateError zoneID:self.testZoneID error:&nserror];
240 XCTAssertNil(nserror, "No error loading other2 from database");
241 XCTAssertNotNil(other2, "Able to re-load other.");
242 XCTAssertEqualObjects(other, other2, "loaded object is equal to object");
245 - (void)testOverwriteCKKSIncomingQueueEntry {
246 NSError* error = nil;
248 CKKSItem* baseitem = [[CKKSItem alloc] initWithUUID: [[NSUUID UUID] UUIDString]
249 parentKeyUUID:[[NSUUID UUID] UUIDString]
250 zoneID:self.testZoneID
251 encItem:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
252 wrappedkey:[[CKKSWrappedAESSIVKey alloc]initWithBase64: @"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="]
255 CKKSIncomingQueueEntry* delete = [[CKKSIncomingQueueEntry alloc] initWithCKKSItem:baseitem
256 action:SecCKKSActionDelete
257 state:SecCKKSStateNew];
258 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
259 NSError* saveError = nil;
260 [delete saveToDatabase:&saveError];
261 XCTAssertNil(saveError, "no error occurred saving delete IQE to database");
262 return CKKSDatabaseTransactionCommit;
265 NSArray<CKKSIncomingQueueEntry*>* entries = [CKKSIncomingQueueEntry all:&error];
266 XCTAssertNil(error, "Should be no error fetching alll IQEs");
267 XCTAssertEqual(entries.count, 1u, "Should be one entry");
268 XCTAssertEqualObjects(entries[0].action, SecCKKSActionDelete, "Should have delete as an action");
270 CKKSIncomingQueueEntry* add = [[CKKSIncomingQueueEntry alloc] initWithCKKSItem:baseitem
271 action:SecCKKSActionAdd
272 state:SecCKKSStateNew];
273 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
274 NSError* saveError = nil;
275 [add saveToDatabase:&saveError];
276 XCTAssertNil(saveError, "no error occurred saving add IQE to database");
277 return CKKSDatabaseTransactionCommit;
280 entries = [CKKSIncomingQueueEntry all:&error];
281 XCTAssertNil(error, "Should be no error fetching alll IQEs");
282 XCTAssertEqual(entries.count, 1u, "Should be one entry");
283 XCTAssertEqualObjects(entries[0].action, SecCKKSActionAdd, "Should have add as an action");
286 -(void)testCKKSZoneStateEntrySQL {
287 CKKSZoneStateEntry* zse = [[CKKSZoneStateEntry alloc] initWithCKZone:@"sqltest"
290 changeToken:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
291 moreRecordsInCloudKit:YES
292 lastFetch:[NSDate date]
293 lastScan:[NSDate date]
294 lastFixup:CKKSCurrentFixupNumber
295 encodedRateLimiter:nil];
296 zse.rateLimiter = [[CKKSRateLimiter alloc] init];
298 CKKSZoneStateEntry* zseClone = [[CKKSZoneStateEntry alloc] initWithCKZone:@"sqltest"
301 changeToken:[@"nonsense" dataUsingEncoding:NSUTF8StringEncoding]
302 moreRecordsInCloudKit:YES
303 lastFetch:zse.lastFetchTime
304 lastScan:zse.lastLocalKeychainScanTime
305 lastFixup:CKKSCurrentFixupNumber
306 encodedRateLimiter:zse.encodedRateLimiter];
308 CKKSZoneStateEntry* zseDifferent = [[CKKSZoneStateEntry alloc] initWithCKZone:@"sqltest"
311 changeToken:[@"allnonsense" dataUsingEncoding:NSUTF8StringEncoding]
312 moreRecordsInCloudKit:NO
313 lastFetch:zse.lastFetchTime
314 lastScan:zse.lastLocalKeychainScanTime
315 lastFixup:CKKSCurrentFixupNumber
316 encodedRateLimiter:zse.encodedRateLimiter];
317 XCTAssertEqualObjects(zse, zseClone, "CKKSZoneStateEntry isEqual of equal objects seems sane");
318 XCTAssertNotEqualObjects(zse, zseDifferent, "CKKSZoneStateEntry isEqual of nonequal objects seems sane");
320 NSError* error = nil;
321 CKKSZoneStateEntry* loaded = [CKKSZoneStateEntry tryFromDatabase: @"sqltest" error:&error];
322 XCTAssertNil(error, "No error trying to load nonexistent record");
323 XCTAssertNil(loaded, "No record saved in database");
325 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
326 NSError* saveError = nil;
327 [zse saveToDatabase:&saveError];
328 XCTAssertNil(saveError, "no error occurred saving CKKSZoneStateEntry to database");
329 return CKKSDatabaseTransactionCommit;
332 loaded = [CKKSZoneStateEntry tryFromDatabase: @"sqltest" error:&error];
333 XCTAssertNil(error, "No error trying to load saved record");
334 XCTAssertNotNil(loaded, "CKKSZoneStateEntry came back out of database");
336 XCTAssertEqualObjects(zse.ckzone, loaded.ckzone, "ckzone persisted through db save and load");
337 XCTAssertEqual (zse.ckzonecreated, loaded.ckzonecreated, "ckzonecreated persisted through db save and load");
338 XCTAssertEqual (zse.ckzonesubscribed, loaded.ckzonesubscribed, "ckzonesubscribed persisted through db save and load");
339 XCTAssertEqualObjects(zse.encodedChangeToken, loaded.encodedChangeToken, "encodedChangeToken persisted through db save and load");
341 secnotice("ckkstests", "zse.lastFetchTime: %@", zse.lastFetchTime);
342 secnotice("ckkstests", "loaded.lastFetchTime: %@", loaded.lastFetchTime);
344 secnotice("ckkstests", "equal?: %d", [zse.lastFetchTime isEqualToDate:loaded.lastFetchTime]);
345 secnotice("ckkstests", "equal to seconds?: %d", [[NSCalendar currentCalendar] isDate:zse.lastFetchTime equalToDate: loaded.lastFetchTime toUnitGranularity:NSCalendarUnitSecond]);
347 // We only compare to the minute level, as that's enough to test the save+load.
348 XCTAssert([[NSCalendar currentCalendar] isDate:zse.lastFetchTime equalToDate: loaded.lastFetchTime toUnitGranularity:NSCalendarUnitMinute],
349 "lastFetchTime persisted through db save and load");
350 XCTAssert([[NSCalendar currentCalendar] isDate:zse.lastLocalKeychainScanTime equalToDate:loaded.lastLocalKeychainScanTime toUnitGranularity:NSCalendarUnitMinute],
351 "lastLocalKeychainScanTime persisted through db save and load");
354 -(void)testRoundtripCKKSDeviceStateEntry {
355 // Very simple test: can these objects roundtrip through the db?
356 NSString* testUUID = @"157A3171-0677-451B-9EAE-0DDC4D4315B0";
357 CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:testUUID
358 osVersion:@"faux-version"
360 octagonPeerID:@"peerID"
363 circleStatus:kSOSCCInCircle
364 keyState:SecCKKSZoneKeyStateReady
365 currentTLKUUID:@"tlk"
366 currentClassAUUID:@"classA"
367 currentClassCUUID:@"classC"
368 zoneID:self.testZoneID
369 encodedCKRecord:nil];
370 XCTAssertNotNil(cdse, "Constructor works");
371 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
372 NSError* saveError = nil;
373 [cdse saveToDatabase:&saveError];
374 XCTAssertNil(saveError, "no error occurred saving cdse to database");
375 return CKKSDatabaseTransactionCommit;
378 NSError* loadError = nil;
379 CKKSDeviceStateEntry* loadedCDSE = [CKKSDeviceStateEntry fromDatabase:testUUID zoneID:self.testZoneID error:&loadError];
380 XCTAssertNil(loadError, "No error loading CDSE");
381 XCTAssertNotNil(loadedCDSE, "Received a CDSE back");
383 XCTAssertEqualObjects(cdse, loadedCDSE, "Roundtripping CKKSDeviceStateEntry ends up with equivalent objects");
386 // disabled, as CKKS syncing is disabled in this class.
387 // To re-enable, need to add flags CKKS syncing to perform queue actions but not automatically start queue processing operations
388 -(void)disabledtestItemAddCreatesCKKSOutgoingQueueEntry {
389 CFMutableDictionaryRef attrs;
394 NSArray* oqes = [CKKSOutgoingQueueEntry all: &error];
395 XCTAssertEqual([oqes count], 0ul, @"Nothing in outgoing queue");
396 XCTAssertNil(error, @"No error loading queue");
398 attrs = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
399 CFDictionarySetValue( attrs, kSecClass, kSecClassGenericPassword );
400 CFDictionarySetValue( attrs, kSecAttrAccessible, kSecAttrAccessibleAlwaysPrivate );
401 CFDictionarySetValue( attrs, kSecAttrLabel, CFSTR( "TestLabel" ) );
402 CFDictionarySetValue( attrs, kSecAttrDescription, CFSTR( "TestDescription" ) );
403 CFDictionarySetValue( attrs, kSecAttrAccount, CFSTR( "TestAccount" ) );
404 CFDictionarySetValue( attrs, kSecAttrService, CFSTR( "TestService" ) );
405 CFDictionarySetValue( attrs, kSecAttrAccessGroup, CFSTR("com.apple.lakitu"));
406 data = CFDataCreate( NULL, (const uint8_t *) "important data", strlen("important data"));
407 CFDictionarySetValue( attrs, kSecValueData, data );
410 XCTAssertEqual(SecItemAdd(attrs, NULL), errSecSuccess, @"Adding item works flawlessly");
412 oqes = [CKKSOutgoingQueueEntry all: &error];
413 XCTAssertEqual([oqes count], 1ul, @"Single entry in outgoing queue after adding item");
414 XCTAssertNil(error, @"No error loading queue");
416 CFDictionarySetValue( attrs, kSecAttrLabel, CFSTR( "TestLabel2" ) );
417 CFDictionarySetValue( attrs, kSecAttrAccount, CFSTR( "TestAccount2" ) );
418 CFDictionarySetValue( attrs, kSecAttrService, CFSTR( "TestService2" ) );
419 XCTAssertEqual(SecItemAdd(attrs, NULL), errSecSuccess);
422 oqes = [CKKSOutgoingQueueEntry all: &error];
423 XCTAssertEqual([oqes count], 2ul, @"Two entries in outgoing queue after adding item");
424 XCTAssertNil(error, @"No error loading queue");
427 - (void)testCKKSKey {
429 NSString* testUUID = @"157A3171-0677-451B-9EAE-0DDC4D4315B0";
430 NSString* testParentUUID = @"f5e7f20f-0885-48f9-b75d-9f0cfd2171b6";
432 NSData* testCKRecord = [@"nonsense" dataUsingEncoding:NSUTF8StringEncoding];
434 CKKSWrappedAESSIVKey* wrappedkey = [[CKKSWrappedAESSIVKey alloc] initWithBase64:@"KFfL58XtugiYNoD859EjG0StfrYd6eakm0CQrgX7iO+DEo4kio3WbEeA1kctCU0GaeTGsRFpbdy4oo6jXhVu7cZqB0svhUPGq55aGnszUjI="];
436 NSError* error = nil;
438 key = [CKKSKey fromDatabase:testUUID zoneID:self.testZoneID error:&error];
439 XCTAssertNil(key, "key does not exist yet");
440 XCTAssertNotNil(error, "error exists when things go wrong");
443 key = [[CKKSKey alloc] initWithWrappedAESKey: wrappedkey
445 parentKeyUUID:testParentUUID
446 keyclass:SecCKKSKeyClassA
447 state: SecCKKSProcessedStateLocal
448 zoneID:self.testZoneID
449 encodedCKRecord:testCKRecord
451 XCTAssertNotNil(key, "could create key");
453 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
454 NSError* saveError = nil;
455 [key saveToDatabase:&saveError];
456 XCTAssertNil(saveError, "no error occurred saving key to database");
457 return CKKSDatabaseTransactionCommit;
461 CKKSKey* key2 = [CKKSKey fromDatabase:testUUID zoneID:self.testZoneID error:&error];
462 XCTAssertNil(error, "no error exists when loading key");
463 XCTAssertNotNil(key2, "key was fetched properly");
465 XCTAssertEqualObjects(key.uuid, key2.uuid, "key uuids match");
466 XCTAssertEqualObjects(key.parentKeyUUID, key2.parentKeyUUID, "parent key uuids match");
467 XCTAssertEqualObjects(key.state, key2.state, "key states match");
468 XCTAssertEqualObjects(key.encodedCKRecord, key2.encodedCKRecord, "encodedCKRecord match");
469 XCTAssertEqualObjects(key.wrappedkey, key2.wrappedkey, "wrapped keys match");
470 XCTAssertEqual(key.currentkey, key2.currentkey, "currentkey match");
474 NSError* error = nil;
476 NSData* testCKRecord = [@"nonsense" dataUsingEncoding:NSUTF8StringEncoding];
478 CKKSKey* tlk = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="]
479 uuid:@"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"
480 keyclass:SecCKKSKeyClassTLK
481 state: SecCKKSProcessedStateLocal
482 zoneID:self.testZoneID
483 encodedCKRecord: testCKRecord
486 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
487 NSError* saveError = nil;
488 [tlk saveToDatabase:&saveError];
489 XCTAssertNil(saveError, "no error occurred saving TLK to database");
490 return CKKSDatabaseTransactionCommit;
493 CKKSKey* wrappedKey = [[CKKSKey alloc] initWrappedBy: tlk
494 AESKey:[CKKSAESSIVKey randomKey:&error]
495 uuid:@"157A3171-0677-451B-9EAE-0DDC4D4315B0"
496 keyclass:SecCKKSKeyClassC
497 state: SecCKKSProcessedStateLocal
498 zoneID:self.testZoneID
499 encodedCKRecord:testCKRecord
501 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
502 NSError* saveError = nil;
503 [wrappedKey saveToDatabase:&saveError];
504 XCTAssertNil(saveError, "no error occurred saving key to database");
505 return CKKSDatabaseTransactionCommit;
508 NSString* secondUUID = @"8b2aeb7f-0000-0000-0000-70d5c728ebf7";
509 CKKSKey* secondtlk = [[CKKSKey alloc] initSelfWrappedWithAESKey:[[CKKSAESSIVKey alloc] initWithBase64: @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="]
511 keyclass:SecCKKSKeyClassTLK
512 state:SecCKKSProcessedStateLocal
513 zoneID:self.testZoneID
514 encodedCKRecord:testCKRecord
516 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
517 NSError* saveError = nil;
518 XCTAssertTrue([secondtlk saveToDatabase:&saveError], "Second TLK saved to database");
519 XCTAssertNil(saveError, "no error occurred saving second TLK to database");
520 return CKKSDatabaseTransactionCommit;
523 NSArray<CKKSKey*>* tlks = [CKKSKey allWhere: @{@"UUID": @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"} error: &error];
524 XCTAssertNotNil(tlks, "Returned some array from allWhere");
525 XCTAssertNil(error, "no error back from allWhere");
526 XCTAssertEqual([tlks count], 1ul, "Received one row (and expected one row)");
528 NSArray<CKKSKey*>* selfWrapped = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals column:CKKSSQLWhereColumnNameUUID]} error: &error];
529 XCTAssertNotNil(selfWrapped, "Returned some array from allWhere");
530 XCTAssertNil(error, "no error back from allWhere");
531 XCTAssertEqual([selfWrapped count], 2ul, "Should have recievied two rows");
533 // Try using CKKSSQLWhereColumn alongside normal binds
534 NSArray<CKKSKey*>* selfWrapped2 = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals column:CKKSSQLWhereColumnNameUUID],
535 @"uuid": @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"}
537 XCTAssertNotNil(selfWrapped2, "Returned some array from allWhere");
538 XCTAssertNil(error, "no error back from allWhere");
539 XCTAssertEqual([selfWrapped2 count], 1ul, "Received one row (and expected one row)");
540 XCTAssertEqualObjects([selfWrapped2[0] uuid], @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7", "Should received first TLK UUID");
542 NSArray<CKKSKey*>* selfWrapped3 = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals column:CKKSSQLWhereColumnNameUUID],
543 @"uuid": [CKKSSQLWhereValue op:CKKSSQLWhereComparatorNotEquals value:@"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"]}
545 XCTAssertNotNil(selfWrapped3, "Returned some array from allWhere");
546 XCTAssertNil(error, "no error back from allWhere");
547 XCTAssertEqual([selfWrapped3 count], 1ul, "Should have received one rows");
548 XCTAssertEqualObjects([selfWrapped3[0] uuid], secondUUID, "Should received second TLK UUID");
550 NSArray<CKKSKey*>* whereFound = [CKKSKey allWhere: @{@"uuid": [[CKKSSQLWhereIn alloc] initWithValues:@[tlk.uuid, wrappedKey.uuid, @"not-found"]]} error:&error];
551 XCTAssertNil(error, "no error back from search");
552 XCTAssertEqual([whereFound count], 2ul, "Should have received two rows");
553 XCTAssertEqualObjects([whereFound[1] uuid], tlk.uuid, "Should received TLK UUID");
554 XCTAssertEqualObjects([whereFound[0] uuid], wrappedKey.uuid, "Should received wrapped key UUID");
557 - (void)testGroupBy {
558 [self addTestZoneEntries];
559 NSError* error = nil;
561 __block NSMutableDictionary<NSString*, NSString*>* results = [[NSMutableDictionary alloc] init];
562 NSDictionary* expectedResults = nil;
564 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
566 columns: @[@"action", @"count(rowid)"]
567 groupBy: @[@"action"]
570 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
571 results[row[@"action"].asString] = row[@"count(rowid)"].asString;
575 XCTAssertNil(error, "no error doing group by query");
577 SecCKKSActionAdd: @"2",
578 SecCKKSActionModify: @"1"
580 XCTAssertEqualObjects(results, expectedResults, "Recieved correct group by result");
582 // Now test with a where clause:
583 results = [[NSMutableDictionary alloc] init];
584 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
585 where: @{@"state": SecCKKSStateError}
586 columns: @[@"action", @"count(rowid)"]
587 groupBy: @[@"action"]
590 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
591 results[row[@"action"].asString] = row[@"count(rowid)"].asString;
595 XCTAssertNil(error, "no error doing where+group by query");
597 SecCKKSActionAdd: @"1",
598 SecCKKSActionModify: @"1"
600 XCTAssertEqualObjects(results, expectedResults, "Recieved correct where+group by result");
603 - (void)testOrderBy {
604 [self addTestZoneEntries];
605 NSError* error = nil;
607 __block NSMutableArray<NSDictionary<NSString*, CKKSSQLResult*>*>* rows = [[NSMutableArray alloc] init];
609 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
611 columns: @[@"action", @"uuid"]
615 processRow:^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
616 [rows addObject:row];
620 XCTAssertNil(error, "no error doing order by query");
621 XCTAssertEqual(rows.count, 3u, "got three items");
623 XCTAssertEqual([rows[0][@"uuid"].asString compare: rows[1][@"uuid"].asString], NSOrderedAscending, "first order is fine");
624 XCTAssertEqual([rows[1][@"uuid"].asString compare: rows[2][@"uuid"].asString], NSOrderedAscending, "second order is fine");
626 // Check that order-by + limit works to page
627 __block NSString* lastUUID = nil;
628 __block NSString* uuid = nil;
631 while(count == 0 || uuid != nil) {
633 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
634 where: lastUUID ? @{@"UUID": [CKKSSQLWhereValue op:CKKSSQLWhereComparatorGreaterThan value:lastUUID]} : nil
635 columns: @[@"action", @"UUID"]
639 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
640 XCTAssertNil(uuid, "Only one row returned");
641 uuid = row[@"UUID"].asString;
644 XCTAssertNil(error, "No error doing SQL");
645 if(uuid && lastUUID) {
646 XCTAssertEqual([lastUUID compare:uuid], NSOrderedAscending, "uuids returning in right order");
651 XCTAssertEqual(count, 4u, "Received 3 objects (and 1 nil)");
655 [self addTestZoneEntries];
656 NSError* error = nil;
658 __block NSMutableDictionary<NSString*, NSString*>* results = [[NSMutableDictionary alloc] init];
660 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
662 columns: @[@"uuid", @"action"]
666 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
667 results[row[@"uuid"].asString] = row[@"action"].asString;
671 XCTAssertNil(error, "no error doing vanilla query");
672 XCTAssertEqual(results.count, 3u, "Received three elements in normal query");
673 results = [[NSMutableDictionary alloc] init];
675 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
677 columns: @[@"uuid", @"action"]
681 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
682 results[row[@"uuid"].asString] = row[@"action"].asString;
686 XCTAssertNil(error, "no error doing limit query");
687 XCTAssertEqual(results.count, 1u, "Received one element in limited query");
688 results = [[NSMutableDictionary alloc] init];
690 // Now test with a where clause:
691 results = [[NSMutableDictionary alloc] init];
692 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
693 where: @{@"state": SecCKKSStateError}
694 columns: @[@"uuid", @"action"]
698 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
699 results[row[@"uuid"].asString] = row[@"action"].asString;
703 XCTAssertNil(error, "no error doing limit+where query");
704 XCTAssertEqual(results.count, 2u, "Received two elements in where+limited query");
705 results = [[NSMutableDictionary alloc] init];
707 results = [[NSMutableDictionary alloc] init];
708 [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable]
709 where: @{@"state": SecCKKSStateError}
710 columns: @[@"uuid", @"action"]
714 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
715 results[row[@"uuid"].asString] = row[@"action"].asString;
719 XCTAssertNil(error, "no error doing limit+where query");
720 XCTAssertEqual(results.count, 1u, "Received one element in where+limited query");
721 results = [[NSMutableDictionary alloc] init];
724 - (void)testQuoting {
725 XCTAssertEqualObjects([CKKSSQLDatabaseObject quotedString:@"hej"], @"hej", "no quote");
726 XCTAssertEqualObjects([CKKSSQLDatabaseObject quotedString:@"hej'"], @"hej''", "single quote");
727 XCTAssertEqualObjects([CKKSSQLDatabaseObject quotedString:@"'hej'"], @"''hej''", "two single quote");
728 XCTAssertEqualObjects([CKKSSQLDatabaseObject quotedString:@"hej\""], @"hej\"", "double quote");
729 XCTAssertEqualObjects([CKKSSQLDatabaseObject quotedString:@"\"hej\""], @"\"hej\"", "double quote");
730 XCTAssertEqualObjects([CKKSSQLDatabaseObject quotedString:@"'\"hej\""], @"''\"hej\"", "double quote");