]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / ckks / tests / CloudKitKeychainSyncingMockXCTest.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 <OCMock/OCMock.h>
27
28 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
29 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
30
31 #import <securityd/SecItemServer.h>
32 #import <securityd/SecItemDb.h>
33
34 #import "keychain/ckks/CKKS.h"
35 #import "keychain/ckks/CKKSKeychainView.h"
36 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
37 #import "keychain/ckks/CKKSItemEncrypter.h"
38 #import "keychain/ckks/CKKSKey.h"
39 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
40 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
41 #import "keychain/ckks/CKKSSynchronizeOperation.h"
42 #import "keychain/ckks/CKKSViewManager.h"
43 #import "keychain/ckks/CKKSZoneStateEntry.h"
44 #import "keychain/ckks/CKKSManifest.h"
45
46 #import "Security/SecureObjectSync/SOSAccount.h"
47 @implementation ZoneKeys
48 @end
49
50 // No tests here, just helper functions
51 @implementation CloudKitKeychainSyncingMockXCTest
52
53 - (void)setUp {
54 // Need to convince your tests to set these, no matter what the on-disk pist says? Uncomment.
55 (void)[CKKSManifest shouldSyncManifests]; // perfrom initialization
56 SecCKKSSetSyncManifests(false);
57 SecCKKSSetEnforceManifests(false);
58
59 // Check that your environment is set up correctly
60 XCTAssertFalse([CKKSManifest shouldSyncManifests], "Manifests syncing is disabled");
61 XCTAssertFalse([CKKSManifest shouldEnforceManifests], "Manifests enforcement is disabled");
62
63 [super setUp];
64 self.keys = [[NSMutableDictionary alloc] init];
65
66 // Fake out whether class A keys can be loaded from the keychain.
67 self.mockCKKSKey = OCMClassMock([CKKSKey class]);
68 __weak __typeof(self) weakSelf = self;
69 OCMStub([self.mockCKKSKey loadKeyMaterialFromKeychain:[OCMArg checkWithBlock:^BOOL(CKKSKey* key) {
70 __strong __typeof(self) strongSelf = weakSelf;
71 return ([key.keyclass isEqualToString: SecCKKSKeyClassA] || [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) && strongSelf.aksLockState;
72 }]
73 resave:[OCMArg anyPointer]
74 error:[OCMArg anyObjectRef]]).andCall(self, @selector(handleLockLoadKeyMaterialFromKeychain:resave:error:));
75
76 OCMStub([self.mockCKKSKey saveKeyMaterialToKeychain:[OCMArg checkWithBlock:^BOOL(CKKSKey* key) {
77 __strong __typeof(self) strongSelf = weakSelf;
78 return ([key.keyclass isEqualToString: SecCKKSKeyClassA] || [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) && strongSelf.aksLockState;
79 }]
80 stashTLK:[OCMArg anyObjectRef]
81 error:[OCMArg anyObjectRef]]
82 ).andCall(self, @selector(handleLockSaveKeyMaterialToKeychain:stashTLK:error:));
83 }
84
85 - (void)tearDown {
86 [self.mockCKKSKey stopMocking];
87 self.mockCKKSKey = nil;
88
89 [super tearDown];
90 self.keys = nil;
91 }
92
93 - (void)createClassCItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account {
94 [self expectCKModifyItemRecords:1
95 currentKeyPointerRecords:1
96 zoneID:zoneID
97 checkItem:[self checkClassCBlock:zoneID message:@"Object was encrypted under class C key in hierarchy"]];
98 [self addGenericPassword: @"data" account: account];
99 OCMVerifyAllWithDelay(self.mockDatabase, 8);
100 }
101
102 - (void)createClassAItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account {
103 [self expectCKModifyItemRecords:1
104 currentKeyPointerRecords:1
105 zoneID:zoneID
106 checkItem: [self checkClassABlock:zoneID message:@"Object was encrypted under class A key in hierarchy"]];
107 [self addGenericPassword:@"asdf"
108 account:account
109 viewHint:nil
110 access:(id)kSecAttrAccessibleWhenUnlocked
111 expecting:errSecSuccess
112 message:@"Adding class A item"];
113 OCMVerifyAllWithDelay(self.mockDatabase, 8);
114 }
115
116 // Helpers to handle keychain 'locked' loading
117 -(bool)handleLockLoadKeyMaterialFromKeychain:(CKKSKey*)key resave:(bool*)resavePtr error:(NSError * __autoreleasing *) error {
118 XCTAssertTrue(self.aksLockState, "Failing a read only when keychain is locked");
119 if(error) {
120 *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
121 }
122 return false;
123 }
124
125 -(bool)handleLockSaveKeyMaterialToKeychain:(CKKSKey*)key stashTLK:(bool)stashTLK error:(NSError * __autoreleasing *) error {
126 XCTAssertTrue(self.aksLockState, "Failing a write only when keychain is locked");
127 if(error) {
128 *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
129 }
130 return false;
131 }
132
133
134 - (ZoneKeys*)createFakeKeyHierarchy: (CKRecordZoneID*)zoneID oldTLK:(CKKSKey*) oldTLK {
135 if(self.keys[zoneID]) {
136 // Already created. Skip.
137 return self.keys[zoneID];
138 }
139
140 NSError* error = nil;
141
142 ZoneKeys* zonekeys = [[ZoneKeys alloc] init];
143
144 zonekeys.tlk = [self fakeTLK:zoneID];
145 [zonekeys.tlk CKRecordWithZoneID: zoneID]; // no-op here, but memoize in the object
146 zonekeys.currentTLKPointer = [[CKKSCurrentKeyPointer alloc] initForClass: SecCKKSKeyClassTLK currentKeyUUID: zonekeys.tlk.uuid zoneID:zoneID encodedCKRecord: nil];
147 [zonekeys.currentTLKPointer CKRecordWithZoneID: zoneID];
148
149 if(oldTLK) {
150 zonekeys.rolledTLK = oldTLK;
151 [zonekeys.rolledTLK wrapUnder: zonekeys.tlk error:&error];
152 XCTAssertNotNil(zonekeys.rolledTLK, "Created a rolled TLK");
153 XCTAssertNil(error, "No error creating rolled TLK");
154 }
155
156 zonekeys.classA = [CKKSKey randomKeyWrappedByParent: zonekeys.tlk keyclass:SecCKKSKeyClassA error:&error];
157 XCTAssertNotNil(zonekeys.classA, "make Class A key");
158 zonekeys.classA.currentkey = true;
159 [zonekeys.classA CKRecordWithZoneID: zoneID];
160 zonekeys.currentClassAPointer = [[CKKSCurrentKeyPointer alloc] initForClass: SecCKKSKeyClassA currentKeyUUID: zonekeys.classA.uuid zoneID:zoneID encodedCKRecord: nil];
161 [zonekeys.currentClassAPointer CKRecordWithZoneID: zoneID];
162
163 zonekeys.classC = [CKKSKey randomKeyWrappedByParent: zonekeys.tlk keyclass:SecCKKSKeyClassC error:&error];
164 XCTAssertNotNil(zonekeys.classC, "make Class C key");
165 zonekeys.classC.currentkey = true;
166 [zonekeys.classC CKRecordWithZoneID: zoneID];
167 zonekeys.currentClassCPointer = [[CKKSCurrentKeyPointer alloc] initForClass: SecCKKSKeyClassC currentKeyUUID: zonekeys.classC.uuid zoneID:zoneID encodedCKRecord: nil];
168 [zonekeys.currentClassCPointer CKRecordWithZoneID: zoneID];
169
170 self.keys[zoneID] = zonekeys;
171 return zonekeys;
172 }
173
174 - (void)saveFakeKeyHierarchyToLocalDatabase: (CKRecordZoneID*)zoneID {
175 NSError* error = nil;
176 ZoneKeys* zonekeys = [self createFakeKeyHierarchy: zoneID oldTLK:nil];
177
178 [zonekeys.tlk saveToDatabase:&error];
179 XCTAssertNil(error, "TLK saved to database successfully");
180
181 [zonekeys.classA saveToDatabase:&error];
182 XCTAssertNil(error, "Class A key saved to database successfully");
183
184 [zonekeys.classC saveToDatabase:&error];
185 XCTAssertNil(error, "Class C key saved to database successfully");
186
187 [zonekeys.currentTLKPointer saveToDatabase:&error];
188 XCTAssertNil(error, "Current TLK pointer saved to database successfully");
189
190 [zonekeys.currentClassAPointer saveToDatabase:&error];
191 XCTAssertNil(error, "Current Class A pointer saved to database successfully");
192
193 [zonekeys.currentClassCPointer saveToDatabase:&error];
194 XCTAssertNil(error, "Current Class C pointer saved to database successfully");
195 }
196
197 - (void)putFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID {
198 ZoneKeys* zonekeys = [self createFakeKeyHierarchy: zoneID oldTLK:nil];
199
200 [self.zones[zoneID] addToZone: zonekeys.tlk zoneID: zoneID];
201 [self.zones[zoneID] addToZone: zonekeys.classA zoneID: zoneID];
202 [self.zones[zoneID] addToZone: zonekeys.classC zoneID: zoneID];
203
204 [self.zones[zoneID] addToZone: zonekeys.currentTLKPointer zoneID: zoneID];
205 [self.zones[zoneID] addToZone: zonekeys.currentClassAPointer zoneID: zoneID];
206 [self.zones[zoneID] addToZone: zonekeys.currentClassCPointer zoneID: zoneID];
207
208 if(zonekeys.rolledTLK) {
209 [self.zones[zoneID] addToZone: zonekeys.rolledTLK zoneID: zoneID];
210 }
211 }
212
213 - (void)rollFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID {
214 ZoneKeys* zonekeys = self.keys[zoneID];
215 self.keys[zoneID] = nil;
216
217 CKKSKey* oldTLK = zonekeys.tlk;
218
219 [self createFakeKeyHierarchy: zoneID oldTLK:oldTLK];
220 [self putFakeKeyHierarchyInCloudKit: zoneID];
221 }
222
223 - (void)saveTLKMaterialToKeychainSimulatingSOS: (CKRecordZoneID*)zoneID {
224
225 XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
226
227 __block CFErrorRef cferror = NULL;
228 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
229 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteSOSTransactionType, &cferror, ^bool {
230 NSError* error = nil;
231 [self.keys[zoneID].tlk saveKeyMaterialToKeychain: false error:&error];
232 XCTAssertNil(error, @"Saved TLK material to keychain");
233 return true;
234 });
235 return ok;
236 });
237
238 XCTAssertNil( (__bridge NSError*)cferror, @"no error with transaction");
239 CFReleaseNull(cferror);
240 }
241 static SOSFullPeerInfoRef SOSCreateFullPeerInfoFromName(CFStringRef name, SecKeyRef* outSigningKey, SecKeyRef* outOctagonSigningKey, CFErrorRef *error)
242 {
243 SOSFullPeerInfoRef result = NULL;
244 SecKeyRef publicKey = NULL;
245 CFDictionaryRef gestalt = NULL;
246
247 *outSigningKey = GeneratePermanentFullECKey(256, name, error);
248
249 *outOctagonSigningKey = GeneratePermanentFullECKey(384, name, error);
250
251 gestalt = SOSCreatePeerGestaltFromName(name);
252
253 result = SOSFullPeerInfoCreate(NULL, gestalt, NULL, *outSigningKey, *outOctagonSigningKey, error);
254
255 CFReleaseNull(gestalt);
256 CFReleaseNull(publicKey);
257
258 return result;
259 }
260 - (NSMutableArray<NSData *>*) SOSPiggyICloudIdentities
261 {
262 SecKeyRef signingKey = NULL;
263 SecKeyRef octagonSigningKey = NULL;
264 NSMutableArray<NSData *>* icloudidentities = [NSMutableArray array];
265
266 SOSFullPeerInfoRef fpi = SOSCreateFullPeerInfoFromName(CFSTR("Test Peer"), &signingKey, &octagonSigningKey, NULL);
267
268 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(SOSFullPeerInfoGetPeerInfo(fpi), NULL));
269 if (data)
270 [icloudidentities addObject:data];
271
272 CFReleaseNull(signingKey);
273 CFReleaseNull(octagonSigningKey);
274
275 return icloudidentities;
276 }
277 static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
278 {
279 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
280 kPIUserDefinedDeviceNameKey, name,
281 NULL);
282 }
283 -(NSMutableDictionary*)SOSPiggyBackCopyFromKeychain
284 {
285 __block NSMutableDictionary *piggybackdata = [[NSMutableDictionary alloc] init];
286 __block CFErrorRef cferror = NULL;
287 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
288 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteSOSTransactionType, &cferror, ^bool {
289 piggybackdata[@"idents"] = [self SOSPiggyICloudIdentities];
290 piggybackdata[@"tlk"] = SOSAccountGetAllTLKs();
291
292 return true;
293 });
294 return ok;
295 });
296
297 XCTAssertNil( (__bridge NSError*)cferror, @"no error with transaction");
298 CFReleaseNull(cferror);
299 return piggybackdata;
300 }
301 - (void)SOSPiggyBackAddToKeychain:(NSDictionary*)piggydata{
302 __block CFErrorRef cferror = NULL;
303 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
304 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteSOSTransactionType, &cferror, ^bool {
305 NSError* error = nil;
306 NSArray* icloudidentities = piggydata[@"idents"];
307 NSArray* tlk = piggydata[@"tlk"];
308
309 SOSPiggyBackAddToKeychain(icloudidentities, tlk);
310
311 XCTAssertNil(error, @"Saved TLK-piggy material to keychain");
312 return true;
313 });
314 return ok;
315 });
316
317 XCTAssertNil( (__bridge NSError*)cferror, @"no error with transaction");
318 CFReleaseNull(cferror);
319 }
320
321 - (void)saveTLKMaterialToKeychain: (CKRecordZoneID*)zoneID {
322 NSError* error = nil;
323 XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
324 [self.keys[zoneID].tlk saveKeyMaterialToKeychain:&error];
325 XCTAssertNil(error, @"Saved TLK material to keychain");
326 }
327
328 - (void)deleteTLKMaterialFromKeychain: (CKRecordZoneID*)zoneID {
329 NSError* error = nil;
330 XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
331 [self.keys[zoneID].tlk deleteKeyMaterialFromKeychain:&error];
332 XCTAssertNil(error, @"Saved TLK material to keychain");
333 }
334
335 - (void)saveClassKeyMaterialToKeychain: (CKRecordZoneID*)zoneID {
336 NSError* error = nil;
337 XCTAssertNotNil(self.keys[zoneID].classA, "Have a Class A to save for zone %@", zoneID);
338 [self.keys[zoneID].classA saveKeyMaterialToKeychain:&error];
339 XCTAssertNil(error, @"Saved Class A material to keychain");
340
341 XCTAssertNotNil(self.keys[zoneID].classC, "Have a Class C to save for zone %@", zoneID);
342 [self.keys[zoneID].classC saveKeyMaterialToKeychain:&error];
343 XCTAssertNil(error, @"Saved Class C material to keychain");
344 }
345
346 - (void)createAndSaveFakeKeyHierarchy: (CKRecordZoneID*)zoneID {
347 [self saveFakeKeyHierarchyToLocalDatabase: zoneID];
348 [self putFakeKeyHierarchyInCloudKit: zoneID];
349 [self saveTLKMaterialToKeychain: zoneID];
350 [self saveClassKeyMaterialToKeychain: zoneID];
351 }
352
353 // Override our base class here:
354 - (void)expectCKModifyKeyRecords: (NSUInteger) expectedNumberOfRecords currentKeyPointerRecords: (NSUInteger) expectedCurrentKeyRecords zoneID: (CKRecordZoneID*) zoneID {
355
356 __weak __typeof(self) weakSelf = self;
357 [self expectCKModifyRecords: @{
358 SecCKRecordIntermediateKeyType: [NSNumber numberWithUnsignedInteger: expectedNumberOfRecords],
359 SecCKRecordCurrentKeyType: [NSNumber numberWithUnsignedInteger: expectedCurrentKeyRecords]}
360 deletedRecordTypeCounts:nil
361 zoneID:zoneID
362 checkModifiedRecord:nil
363 runAfterModification:^{
364 __strong __typeof(weakSelf) strongSelf = weakSelf;
365 XCTAssertNotNil(strongSelf, "self exists");
366
367 // Reach into our cloudkit database and extract the keys
368 CKRecordID* currentTLKPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassTLK zoneID:zoneID];
369 CKRecordID* currentClassAPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassA zoneID:zoneID];
370 CKRecordID* currentClassCPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassC zoneID:zoneID];
371
372 ZoneKeys* zonekeys = strongSelf.keys[zoneID];
373 if(!zonekeys) {
374 zonekeys = [[ZoneKeys alloc] init];
375 strongSelf.keys[zoneID] = zonekeys;
376 }
377
378 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID], "Have a currentTLKPointer");
379 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID], "Have a currentClassAPointer");
380 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID], "Have a currentClassCPointer");
381 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey], "Have a currentTLKPointer parent");
382 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassAPointer parent");
383 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassCPointer parent");
384 XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentTLKPointer parent UUID");
385 XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassAPointer parent UUID");
386 XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassCPointer parent UUID");
387
388 zonekeys.currentTLKPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID]];
389 zonekeys.currentClassAPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID]];
390 zonekeys.currentClassCPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID]];
391
392 XCTAssertNotNil(zonekeys.currentTLKPointer.currentKeyUUID, "Have a currentTLKPointer current UUID");
393 XCTAssertNotNil(zonekeys.currentClassAPointer.currentKeyUUID, "Have a currentClassAPointer current UUID");
394 XCTAssertNotNil(zonekeys.currentClassCPointer.currentKeyUUID, "Have a currentClassCPointer current UUID");
395
396 CKRecordID* currentTLKID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentTLKPointer.currentKeyUUID zoneID:zoneID];
397 CKRecordID* currentClassAID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassAPointer.currentKeyUUID zoneID:zoneID];
398 CKRecordID* currentClassCID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassCPointer.currentKeyUUID zoneID:zoneID];
399
400 zonekeys.tlk = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKID]];
401 zonekeys.classA = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAID]];
402 zonekeys.classC = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCID]];
403
404 XCTAssertNotNil(zonekeys.tlk, "Have the current TLK");
405 XCTAssertNotNil(zonekeys.classA, "Have the current Class A key");
406 XCTAssertNotNil(zonekeys.classC, "Have the current Class C key");
407 }];
408 }
409
410 - (void)checkNoCKKSData: (CKKSKeychainView*) view {
411 // Test that there are no items in the database
412 [view dispatchSync:^bool{
413 NSError* error = nil;
414 NSArray<CKKSMirrorEntry*>* ckmes = [CKKSMirrorEntry all: view.zoneID error:&error];
415 XCTAssertNil(error, "No error fetching CKMEs");
416 XCTAssertEqual(ckmes.count, 0ul, "No CKMirrorEntries");
417
418 NSArray<CKKSOutgoingQueueEntry*>* oqes = [CKKSOutgoingQueueEntry all: view.zoneID error:&error];
419 XCTAssertNil(error, "No error fetching OQEs");
420 XCTAssertEqual(oqes.count, 0ul, "No OutgoingQueueEntries");
421
422 NSArray<CKKSIncomingQueueEntry*>* iqes = [CKKSIncomingQueueEntry all: view.zoneID error:&error];
423 XCTAssertNil(error, "No error fetching IQEs");
424 XCTAssertEqual(iqes.count, 0ul, "No IncomingQueueEntries");
425
426 NSArray<CKKSKey*>* keys = [CKKSKey all: view.zoneID error:&error];
427 XCTAssertNil(error, "No error fetching keys");
428 XCTAssertEqual(keys.count, 0ul, "No CKKSKeys");
429
430 NSArray<CKKSDeviceStateEntry*>* deviceStates = [CKKSDeviceStateEntry allInZone:view.zoneID error:&error];
431 XCTAssertNil(error, "should be no error fetching device states");
432 XCTAssertEqual(deviceStates.count, 0ul, "No Device State entries");
433 return false;
434 }];
435 }
436
437 - (BOOL (^) (CKRecord*)) checkClassABlock: (CKRecordZoneID*) zoneID message:(NSString*) message {
438 __weak __typeof(self) weakSelf = self;
439 return ^BOOL(CKRecord* record) {
440 __strong __typeof(weakSelf) strongSelf = weakSelf;
441 XCTAssertNotNil(strongSelf, "self exists");
442
443 ZoneKeys* zoneKeys = strongSelf.keys[zoneID];
444 XCTAssertNotNil(zoneKeys, "Have zone keys for %@", zoneID);
445 XCTAssertEqualObjects([record[SecCKRecordParentKeyRefKey] recordID].recordName, zoneKeys.classA.uuid, "%@", message);
446 return [[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqual: zoneKeys.classA.uuid];
447 };
448 }
449
450 - (BOOL (^) (CKRecord*)) checkClassCBlock: (CKRecordZoneID*) zoneID message:(NSString*) message {
451 __weak __typeof(self) weakSelf = self;
452 return ^BOOL(CKRecord* record) {
453 __strong __typeof(weakSelf) strongSelf = weakSelf;
454 XCTAssertNotNil(strongSelf, "self exists");
455
456 ZoneKeys* zoneKeys = strongSelf.keys[zoneID];
457 XCTAssertNotNil(zoneKeys, "Have zone keys for %@", zoneID);
458 XCTAssertEqualObjects([record[SecCKRecordParentKeyRefKey] recordID].recordName, zoneKeys.classC.uuid, "%@", message);
459 return [[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqual: zoneKeys.classC.uuid];
460 };
461 }
462
463 - (BOOL (^) (CKRecord*)) checkPasswordBlock:(CKRecordZoneID*)zoneID
464 account:(NSString*)account
465 password:(NSString*)password {
466 __weak __typeof(self) weakSelf = self;
467 return ^BOOL(CKRecord* record) {
468 __strong __typeof(weakSelf) strongSelf = weakSelf;
469 XCTAssertNotNil(strongSelf, "self exists");
470
471 ZoneKeys* zoneKeys = strongSelf.keys[zoneID];
472 XCTAssertNotNil(zoneKeys, "Have zone keys for %@", zoneID);
473 XCTAssertNotNil([record[SecCKRecordParentKeyRefKey] recordID].recordName, "Have a wrapping key");
474
475 CKKSKey* key = nil;
476 if([[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqualToString: zoneKeys.classC.uuid]) {
477 key = zoneKeys.classC;
478 } else if([[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqualToString: zoneKeys.classA.uuid]) {
479 key = zoneKeys.classA;
480 }
481 XCTAssertNotNil(key, "Found a key via UUID");
482
483 CKKSMirrorEntry* ckme = [[CKKSMirrorEntry alloc] initWithCKRecord: record];
484
485 NSError* error = nil;
486 NSDictionary* dict = [CKKSItemEncrypter decryptItemToDictionary:ckme.item error:&error];
487 XCTAssertNil(error, "No error decrypting item");
488 XCTAssertEqualObjects(account, dict[(id)kSecAttrAccount], "Account matches");
489 XCTAssertEqualObjects([password dataUsingEncoding:NSUTF8StringEncoding], dict[(id)kSecValueData], "Password matches");
490 return YES;
491 };
492 }
493
494 - (NSDictionary*)fakeRecordDictionary:(NSString*) account zoneID:(CKRecordZoneID*)zoneID {
495 NSError* error = nil;
496
497 /* Basically: @{
498 @"acct" : @"account-delete-me",
499 @"agrp" : @"com.apple.security.sos",
500 @"cdat" : @"2016-12-21 03:33:25 +0000",
501 @"class" : @"genp",
502 @"mdat" : @"2016-12-21 03:33:25 +0000",
503 @"musr" : [[NSData alloc] init],
504 @"pdmn" : @"ak",
505 @"sha1" : [[NSData alloc] initWithBase64EncodedString: @"C3VWONaOIj8YgJjk/xwku4By1CY=" options:0],
506 @"svce" : @"",
507 @"tomb" : [NSNumber numberWithInt: 0],
508 @"v_Data" : [@"data" dataUsingEncoding: NSUTF8StringEncoding],
509 };
510 TODO: this should be binary encoded instead of expanded, but the plist encoder should handle it fine */
511 NSData* itemdata = [[NSData alloc] initWithBase64EncodedString:@"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YWNjdDwva2V5PgoJPHN0cmluZz5hY2NvdW50LWRlbGV0ZS1tZTwvc3RyaW5nPgoJPGtleT5hZ3JwPC9rZXk+Cgk8c3RyaW5nPmNvbS5hcHBsZS5zZWN1cml0eS5zb3M8L3N0cmluZz4KCTxrZXk+Y2RhdDwva2V5PgoJPGRhdGU+MjAxNi0xMi0yMVQwMzozMzoyNVo8L2RhdGU+Cgk8a2V5PmNsYXNzPC9rZXk+Cgk8c3RyaW5nPmdlbnA8L3N0cmluZz4KCTxrZXk+bWRhdDwva2V5PgoJPGRhdGU+MjAxNi0xMi0yMVQwMzozMzoyNVo8L2RhdGU+Cgk8a2V5Pm11c3I8L2tleT4KCTxkYXRhPgoJPC9kYXRhPgoJPGtleT5wZG1uPC9rZXk+Cgk8c3RyaW5nPmFrPC9zdHJpbmc+Cgk8a2V5PnNoYTE8L2tleT4KCTxkYXRhPgoJQzNWV09OYU9JajhZZ0pqay94d2t1NEJ5MUNZPQoJPC9kYXRhPgoJPGtleT5zdmNlPC9rZXk+Cgk8c3RyaW5nPjwvc3RyaW5nPgoJPGtleT50b21iPC9rZXk+Cgk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJPGtleT52X0RhdGE8L2tleT4KCTxkYXRhPgoJWkdGMFlRPT0KCTwvZGF0YT4KPC9kaWN0Pgo8L3BsaXN0Pgo=" options:0];
512 NSMutableDictionary * item = [[NSPropertyListSerialization propertyListWithData:itemdata
513 options:0
514 format:nil
515 error:&error] mutableCopy];
516 // Fix up dictionary
517 item[@"agrp"] = @"com.apple.security.ckks";
518 item[@"vwht"] = @"keychain";
519 XCTAssertNil(error, "no error interpreting data as item");
520 XCTAssertNotNil(item, "interpreted data as item");
521
522 if(zoneID && ![zoneID.zoneName isEqualToString:@"keychain"]) {
523 [item setObject: zoneID.zoneName forKey: (__bridge id) kSecAttrSyncViewHint];
524 }
525
526 if(account) {
527 [item setObject: account forKey: (__bridge id) kSecAttrAccount];
528 }
529 return item;
530 }
531
532 - (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName {
533 return [self createFakeRecord: zoneID recordName:recordName withAccount: nil key:nil];
534 }
535
536 - (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount: (NSString*) account {
537 return [self createFakeRecord: zoneID recordName:recordName withAccount:account key:nil];
538 }
539
540 - (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount: (NSString*) account key:(CKKSKey*)key {
541 NSDictionary* item = [self fakeRecordDictionary: account zoneID:zoneID];
542
543 CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:recordName zoneID:zoneID];
544 if(key) {
545 return [self newRecord: ckrid withNewItemData:item key:key];
546 } else {
547 return [self newRecord: ckrid withNewItemData:item];
548 }
549 }
550
551 - (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary {
552 ZoneKeys* zonekeys = self.keys[recordID.zoneID];
553 XCTAssertNotNil(zonekeys, "Have zone keys for zone");
554 XCTAssertNotNil(zonekeys.classC, "Have class C key for zone");
555
556 return [self newRecord:recordID withNewItemData:dictionary key:zonekeys.classC];
557 }
558
559 - (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary key:(CKKSKey*)key {
560 NSError* error = nil;
561 CKKSItem* cipheritem = [CKKSItemEncrypter encryptCKKSItem:[[CKKSItem alloc] initWithUUID:recordID.recordName
562 parentKeyUUID:key.uuid
563 zoneID:recordID.zoneID]
564 dataDictionary:dictionary
565 updatingCKKSItem:nil
566 parentkey:key
567 error:&error];
568 XCTAssertNil(error, "encrypted item with class c key");
569 XCTAssertNotNil(cipheritem, "Have an encrypted item");
570
571 CKRecord* ckr = [cipheritem CKRecordWithZoneID: recordID.zoneID];
572 XCTAssertNotNil(ckr, "Created a CKRecord");
573 return ckr;
574 }
575
576 - (NSDictionary*)decryptRecord: (CKRecord*) record {
577 CKKSItem* item = [[CKKSItem alloc] initWithCKRecord: record];
578
579 NSError* error = nil;
580
581 NSDictionary* ret = [CKKSItemEncrypter decryptItemToDictionary: item error:&error];
582 XCTAssertNil(error);
583 XCTAssertNotNil(ret);
584 return ret;
585 }
586
587 - (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint: (NSString*) viewHint access: (NSString*) access expecting: (OSStatus) status message: (NSString*) message {
588 NSMutableDictionary* query = [@{
589 (id)kSecClass : (id)kSecClassGenericPassword,
590 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
591 (id)kSecAttrAccessible: access,
592 (id)kSecAttrAccount : account,
593 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
594 (id)kSecValueData : (id) [password dataUsingEncoding:NSUTF8StringEncoding],
595 } mutableCopy];
596
597 if(viewHint) {
598 query[(id)kSecAttrSyncViewHint] = viewHint;
599 } else {
600 // Fake it as 'keychain'. This lets CKKSScanLocalItemsOperation for the test-only 'keychain' view find items which would normally not have a view hint.
601 query[(id)kSecAttrSyncViewHint] = @"keychain";
602 }
603
604 XCTAssertEqual(status, SecItemAdd((__bridge CFDictionaryRef) query, NULL), @"%@", message);
605 }
606
607 - (void)addGenericPassword: (NSString*) password account: (NSString*) account expecting: (OSStatus) status message: (NSString*) message {
608 [self addGenericPassword:password account:account viewHint:nil access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:message];
609
610 }
611
612 - (void)addGenericPassword: (NSString*) password account: (NSString*) account {
613 [self addGenericPassword:password account:account viewHint:nil access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:@"Add item to keychain"];
614 }
615
616 - (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint:(NSString*)viewHint {
617 [self addGenericPassword:password account:account viewHint:viewHint access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:@"Add item to keychain with a viewhint"];
618 }
619
620 - (void)updateGenericPassword: (NSString*) newPassword account: (NSString*)account {
621 NSDictionary* query = @{
622 (id)kSecClass : (id)kSecClassGenericPassword,
623 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
624 (id)kSecAttrAccount : account,
625 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
626 };
627
628 NSDictionary* update = @{
629 (id)kSecValueData : (id) [newPassword dataUsingEncoding:NSUTF8StringEncoding],
630 };
631 XCTAssertEqual(errSecSuccess, SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update), @"Updating item %@ to %@", account, newPassword);
632 }
633
634 - (void)updateAccountOfGenericPassword:(NSString*)newAccount
635 account:(NSString*)account {
636 NSDictionary* query = @{
637 (id)kSecClass : (id)kSecClassGenericPassword,
638 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
639 (id)kSecAttrAccount : account,
640 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
641 };
642
643 NSDictionary* update = @{
644 (id)kSecAttrAccount : (id) newAccount,
645 };
646 XCTAssertEqual(errSecSuccess, SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update), @"Updating item %@ to account %@", account, newAccount);
647 }
648
649 - (void)deleteGenericPassword: (NSString*) account {
650 NSDictionary* query = @{
651 (id)kSecClass : (id)kSecClassGenericPassword,
652 (id)kSecAttrAccount : account,
653 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
654 };
655
656 XCTAssertEqual(errSecSuccess, SecItemDelete((__bridge CFDictionaryRef) query), @"Deleting item %@", account);
657 }
658
659 - (void)findGenericPassword: (NSString*) account expecting: (OSStatus) status {
660 NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword,
661 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
662 (id)kSecAttrAccount : account,
663 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
664 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
665 };
666 XCTAssertEqual(status, SecItemCopyMatching((__bridge CFDictionaryRef) query, NULL), "Finding item %@", account);
667 }
668
669 - (void)checkGenericPassword: (NSString*) password account: (NSString*) account {
670 NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword,
671 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
672 (id)kSecAttrAccount : account,
673 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
674 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
675 (id)kSecReturnData : (id)kCFBooleanTrue,
676 };
677 CFTypeRef result = NULL;
678
679 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef) query, &result), "Finding item %@", account);
680 XCTAssertNotNil((__bridge id)result, "Received an item");
681
682 NSString* storedPassword = [[NSString alloc] initWithData: (__bridge NSData*) result encoding: NSUTF8StringEncoding];
683 XCTAssertNotNil(storedPassword, "Password parsed as a password");
684
685 XCTAssertEqualObjects(storedPassword, password, "Stored password matches received password");
686 }
687
688 -(XCTestExpectation*)expectChangeForView:(NSString*)view {
689 NSString* notification = [NSString stringWithFormat: @"com.apple.security.view-change.%@", view];
690 return [self expectationForNotification:notification object:nil handler:^BOOL(NSNotification * _Nonnull nsnotification) {
691 secnotice("ckks", "Got a notification for %@: %@", notification, nsnotification);
692 return YES;
693 }];
694 }
695
696 - (void)checkNSyncableTLKsInKeychain:(size_t)n {
697 NSDictionary *query = @{(id)kSecClass : (id)kSecClassInternetPassword,
698 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
699 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
700 (id)kSecAttrDescription: SecCKKSKeyClassTLK,
701 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
702 (id)kSecReturnAttributes : (id)kCFBooleanTrue,
703 };
704 CFTypeRef result = NULL;
705
706 if(n == 0) {
707 XCTAssertEqual(errSecItemNotFound, SecItemCopyMatching((__bridge CFDictionaryRef) query, &result), "Should have found no TLKs");
708 } else {
709 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef) query, &result), "Should have found TLKs");
710 NSArray* items = (NSArray*) CFBridgingRelease(result);
711
712 XCTAssertEqual(items.count, n, "Should have received %lu items", n);
713 }
714 }
715
716 @end
717
718 #endif // OCTAGON