]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m
Security-58286.41.2.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 #import "keychain/ckks/CKKSPeer.h"
46
47 #pragma clang diagnostic push
48 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
49 #import "Security/SecureObjectSync/SOSAccount.h"
50 #pragma clang diagnostic pop
51
52 @implementation ZoneKeys
53 - (instancetype)initLoadingRecordsFromZone:(FakeCKZone*)zone {
54 if((self = [super init])) {
55 CKRecordID* currentTLKPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassTLK zoneID:zone.zoneID];
56 CKRecordID* currentClassAPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassA zoneID:zone.zoneID];
57 CKRecordID* currentClassCPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassC zoneID:zone.zoneID];
58
59 CKRecord* currentTLKPointerRecord = zone.currentDatabase[currentTLKPointerID];
60 CKRecord* currentClassAPointerRecord = zone.currentDatabase[currentClassAPointerID];
61 CKRecord* currentClassCPointerRecord = zone.currentDatabase[currentClassCPointerID];
62
63 self.currentTLKPointer = currentTLKPointerRecord ? [[CKKSCurrentKeyPointer alloc] initWithCKRecord: currentTLKPointerRecord] : nil;
64 self.currentClassAPointer = currentClassAPointerRecord ? [[CKKSCurrentKeyPointer alloc] initWithCKRecord: currentClassAPointerRecord] : nil;
65 self.currentClassCPointer = currentClassCPointerRecord ? [[CKKSCurrentKeyPointer alloc] initWithCKRecord: currentClassCPointerRecord] : nil;
66
67 CKRecordID* currentTLKID = self.currentTLKPointer.currentKeyUUID ? [[CKRecordID alloc] initWithRecordName:self.currentTLKPointer.currentKeyUUID zoneID:zone.zoneID] : nil;
68 CKRecordID* currentClassAID = self.currentClassAPointer.currentKeyUUID ? [[CKRecordID alloc] initWithRecordName:self.currentClassAPointer.currentKeyUUID zoneID:zone.zoneID] : nil;
69 CKRecordID* currentClassCID = self.currentClassCPointer.currentKeyUUID ? [[CKRecordID alloc] initWithRecordName:self.currentClassCPointer.currentKeyUUID zoneID:zone.zoneID] : nil;
70
71 CKRecord* currentTLKRecord = currentTLKID ? zone.currentDatabase[currentTLKID] : nil;
72 CKRecord* currentClassARecord = currentClassAID ? zone.currentDatabase[currentClassAID] : nil;
73 CKRecord* currentClassCRecord = currentClassCID ? zone.currentDatabase[currentClassCID] : nil;
74
75 self.tlk = currentTLKRecord ? [[CKKSKey alloc] initWithCKRecord: currentTLKRecord] : nil;
76 self.classA = currentClassARecord ? [[CKKSKey alloc] initWithCKRecord: currentClassARecord] : nil;
77 self.classC = currentClassCRecord ? [[CKKSKey alloc] initWithCKRecord: currentClassCRecord] : nil;
78 }
79 return self;
80 }
81
82 @end
83
84 // No tests here, just helper functions
85 @implementation CloudKitKeychainSyncingMockXCTest
86
87 - (void)setUp {
88 // Need to convince your tests to set these, no matter what the on-disk plist says? Uncomment.
89 (void)[CKKSManifest shouldSyncManifests]; // perfrom initialization
90 SecCKKSSetSyncManifests(false);
91 SecCKKSSetEnforceManifests(false);
92
93 // Check that your environment is set up correctly
94 XCTAssertFalse([CKKSManifest shouldSyncManifests], "Manifests syncing is disabled");
95 XCTAssertFalse([CKKSManifest shouldEnforceManifests], "Manifests enforcement is disabled");
96
97 [super setUp];
98 self.ckksZones = [NSMutableSet set];
99 self.keys = [[NSMutableDictionary alloc] init];
100
101 // Fake out whether class A keys can be loaded from the keychain.
102 self.mockCKKSKey = OCMClassMock([CKKSKey class]);
103 __weak __typeof(self) weakSelf = self;
104 BOOL (^shouldFailKeychainQuery)(NSDictionary* query) = ^BOOL(NSDictionary* query) {
105 __strong __typeof(self) strongSelf = weakSelf;
106 NSString* description = query[(id)kSecAttrDescription];
107 bool isTLK = [description isEqualToString: SecCKKSKeyClassTLK] ||
108 [description isEqualToString: [SecCKKSKeyClassTLK stringByAppendingString: @"-nonsync"]] ||
109 [description isEqualToString: [SecCKKSKeyClassTLK stringByAppendingString: @"-piggy"]];
110 bool isClassA = [description isEqualToString: SecCKKSKeyClassA];
111
112 return (isTLK || isClassA) && strongSelf.aksLockState;
113 };
114
115 OCMStub([self.mockCKKSKey setKeyMaterialInKeychain:[OCMArg checkWithBlock:shouldFailKeychainQuery] error:[OCMArg anyObjectRef]]
116 ).andCall(self, @selector(handleLockSetKeyMaterialInKeychain:error:));
117
118 OCMStub([self.mockCKKSKey queryKeyMaterialInKeychain:[OCMArg checkWithBlock:shouldFailKeychainQuery] error:[OCMArg anyObjectRef]]
119 ).andCall(self, @selector(handleLockLoadKeyMaterialFromKeychain:error:));
120
121 // Fake out SOS peers
122 self.currentSelfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"local-peer"
123 encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
124 signingKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]];
125
126 // No trusted non-self peers by default. Your test can change this if it wants.
127 self.currentPeers = [NSMutableSet set];
128
129 OCMStub([self.mockCKKSViewManager fetchSelfPeers:[OCMArg anyObjectRef]]).andCall(self, @selector(fetchSelfPeers:));
130 OCMStub([self.mockCKKSViewManager fetchTrustedPeers:[OCMArg anyObjectRef]]).andCall(self, @selector(fetchTrustedPeers:));
131
132 // Bring up a fake CKKSControl object
133 id mockConnection = OCMPartialMock([[NSXPCConnection alloc] init]);
134 OCMStub([mockConnection remoteObjectProxyWithErrorHandler:[OCMArg any]]).andCall(self, @selector(injectedManager));
135 self.ckksControl = [[CKKSControl alloc] initWithConnection:mockConnection];
136 XCTAssertNotNil(self.ckksControl, "Should have received control object");
137 }
138
139 - (void)tearDown {
140 [self.mockCKKSKey stopMocking];
141 self.mockCKKSKey = nil;
142
143 [super tearDown];
144 self.keys = nil;
145
146 self.currentSelfPeer = nil;
147 self.currentPeers = nil;
148 }
149
150 - (CKKSSelves*)fetchSelfPeers:(NSError* __autoreleasing *)error {
151 //
152 if(self.aksLockState) {
153 if(error) {
154 *error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:nil];
155 }
156 return nil;
157 } else {
158 // Only supports a single self peer for now
159 return [[CKKSSelves alloc] initWithCurrent:self.currentSelfPeer allSelves:nil];
160 }
161 }
162
163 - (NSSet<id<CKKSPeer>>*)fetchTrustedPeers:(NSError* __autoreleasing *)error {
164 // Trusted Peers include ourselves, but as a CKKSSOSPeer object instead of a self peer
165 CKKSSOSPeer* s = [[CKKSSOSPeer alloc] initWithSOSPeerID:self.currentSelfPeer.peerID
166 encryptionPublicKey:self.currentSelfPeer.publicEncryptionKey
167 signingPublicKey:self.currentSelfPeer.publicSigningKey];
168
169 return [self.currentPeers setByAddingObject: s];
170 }
171
172 - (void)createClassCItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account {
173 [self expectCKModifyItemRecords:1
174 currentKeyPointerRecords:1
175 zoneID:zoneID
176 checkItem:[self checkClassCBlock:zoneID message:@"Object was encrypted under class C key in hierarchy"]];
177 [self addGenericPassword: @"data" account: account];
178 OCMVerifyAllWithDelay(self.mockDatabase, 8);
179 }
180
181 - (void)createClassAItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account {
182 [self expectCKModifyItemRecords:1
183 currentKeyPointerRecords:1
184 zoneID:zoneID
185 checkItem: [self checkClassABlock:zoneID message:@"Object was encrypted under class A key in hierarchy"]];
186 [self addGenericPassword:@"asdf"
187 account:account
188 viewHint:nil
189 access:(id)kSecAttrAccessibleWhenUnlocked
190 expecting:errSecSuccess
191 message:@"Adding class A item"];
192 OCMVerifyAllWithDelay(self.mockDatabase, 8);
193 }
194
195 // Helpers to handle 'locked' keychain loading and saving
196 -(bool)handleLockLoadKeyMaterialFromKeychain:(NSDictionary*)query error:(NSError * __autoreleasing *) error {
197 // I think the behavior is: errSecItemNotFound if the item doesn't exist, otherwise errSecInteractionNotAllowed.
198 XCTAssertTrue(self.aksLockState, "Failing a read when keychain is locked");
199
200 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
201 if(status == errSecItemNotFound) {
202 if(error) {
203 *error = [NSError errorWithDomain:@"securityd" code:status userInfo:nil];
204 }
205 } else {
206 if(error) {
207 *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
208 }
209 }
210 return false;
211 }
212
213 -(bool)handleLockSetKeyMaterialInKeychain:(NSDictionary*)query error:(NSError * __autoreleasing *) error {
214 XCTAssertTrue(self.aksLockState, "Failing a write only when keychain is locked");
215 if(error) {
216 *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
217 }
218 return false;
219 }
220
221
222 - (ZoneKeys*)createFakeKeyHierarchy: (CKRecordZoneID*)zoneID oldTLK:(CKKSKey*) oldTLK {
223 if(self.keys[zoneID]) {
224 // Already created. Skip.
225 return self.keys[zoneID];
226 }
227
228 NSError* error = nil;
229
230 ZoneKeys* zonekeys = [[ZoneKeys alloc] init];
231
232 zonekeys.tlk = [self fakeTLK:zoneID];
233 [zonekeys.tlk CKRecordWithZoneID: zoneID]; // no-op here, but memoize in the object
234 zonekeys.currentTLKPointer = [[CKKSCurrentKeyPointer alloc] initForClass: SecCKKSKeyClassTLK currentKeyUUID: zonekeys.tlk.uuid zoneID:zoneID encodedCKRecord: nil];
235 [zonekeys.currentTLKPointer CKRecordWithZoneID: zoneID];
236
237 if(oldTLK) {
238 zonekeys.rolledTLK = oldTLK;
239 [zonekeys.rolledTLK wrapUnder: zonekeys.tlk error:&error];
240 XCTAssertNotNil(zonekeys.rolledTLK, "Created a rolled TLK");
241 XCTAssertNil(error, "No error creating rolled TLK");
242 }
243
244 zonekeys.classA = [CKKSKey randomKeyWrappedByParent: zonekeys.tlk keyclass:SecCKKSKeyClassA error:&error];
245 XCTAssertNotNil(zonekeys.classA, "make Class A key");
246 zonekeys.classA.currentkey = true;
247 [zonekeys.classA CKRecordWithZoneID: zoneID];
248 zonekeys.currentClassAPointer = [[CKKSCurrentKeyPointer alloc] initForClass: SecCKKSKeyClassA currentKeyUUID: zonekeys.classA.uuid zoneID:zoneID encodedCKRecord: nil];
249 [zonekeys.currentClassAPointer CKRecordWithZoneID: zoneID];
250
251 zonekeys.classC = [CKKSKey randomKeyWrappedByParent: zonekeys.tlk keyclass:SecCKKSKeyClassC error:&error];
252 XCTAssertNotNil(zonekeys.classC, "make Class C key");
253 zonekeys.classC.currentkey = true;
254 [zonekeys.classC CKRecordWithZoneID: zoneID];
255 zonekeys.currentClassCPointer = [[CKKSCurrentKeyPointer alloc] initForClass: SecCKKSKeyClassC currentKeyUUID: zonekeys.classC.uuid zoneID:zoneID encodedCKRecord: nil];
256 [zonekeys.currentClassCPointer CKRecordWithZoneID: zoneID];
257
258 self.keys[zoneID] = zonekeys;
259 return zonekeys;
260 }
261
262 - (void)saveFakeKeyHierarchyToLocalDatabase: (CKRecordZoneID*)zoneID {
263 NSError* error = nil;
264 ZoneKeys* zonekeys = [self createFakeKeyHierarchy: zoneID oldTLK:nil];
265
266 [zonekeys.tlk saveToDatabase:&error];
267 XCTAssertNil(error, "TLK saved to database successfully");
268
269 [zonekeys.classA saveToDatabase:&error];
270 XCTAssertNil(error, "Class A key saved to database successfully");
271
272 [zonekeys.classC saveToDatabase:&error];
273 XCTAssertNil(error, "Class C key saved to database successfully");
274
275 [zonekeys.currentTLKPointer saveToDatabase:&error];
276 XCTAssertNil(error, "Current TLK pointer saved to database successfully");
277
278 [zonekeys.currentClassAPointer saveToDatabase:&error];
279 XCTAssertNil(error, "Current Class A pointer saved to database successfully");
280
281 [zonekeys.currentClassCPointer saveToDatabase:&error];
282 XCTAssertNil(error, "Current Class C pointer saved to database successfully");
283 }
284
285 - (void)putFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID {
286 ZoneKeys* zonekeys = [self createFakeKeyHierarchy: zoneID oldTLK:nil];
287
288 [self.zones[zoneID] addToZone: zonekeys.tlk zoneID: zoneID];
289 [self.zones[zoneID] addToZone: zonekeys.classA zoneID: zoneID];
290 [self.zones[zoneID] addToZone: zonekeys.classC zoneID: zoneID];
291
292 [self.zones[zoneID] addToZone: zonekeys.currentTLKPointer zoneID: zoneID];
293 [self.zones[zoneID] addToZone: zonekeys.currentClassAPointer zoneID: zoneID];
294 [self.zones[zoneID] addToZone: zonekeys.currentClassCPointer zoneID: zoneID];
295
296 if(zonekeys.rolledTLK) {
297 [self.zones[zoneID] addToZone: zonekeys.rolledTLK zoneID: zoneID];
298 }
299 }
300
301 - (void)rollFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID {
302 ZoneKeys* zonekeys = self.keys[zoneID];
303 self.keys[zoneID] = nil;
304
305 CKKSKey* oldTLK = zonekeys.tlk;
306 NSError* error = nil;
307 [oldTLK ensureKeyLoaded:&error];
308 XCTAssertNil(error, "shouldn't error ensuring that the oldTLK has its key material");
309
310 [self createFakeKeyHierarchy: zoneID oldTLK:oldTLK];
311 [self putFakeKeyHierarchyInCloudKit: zoneID];
312 }
313
314 - (void)saveTLKMaterialToKeychainSimulatingSOS: (CKRecordZoneID*)zoneID {
315
316 XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
317
318 __block CFErrorRef cferror = NULL;
319 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
320 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteSOSTransactionType, &cferror, ^bool {
321 NSError* error = nil;
322 [self.keys[zoneID].tlk saveKeyMaterialToKeychain: false error:&error];
323 XCTAssertNil(error, @"Saved TLK material to keychain");
324 return true;
325 });
326 return ok;
327 });
328
329 XCTAssertNil( (__bridge NSError*)cferror, @"no error with transaction");
330 CFReleaseNull(cferror);
331 }
332 static SOSFullPeerInfoRef SOSCreateFullPeerInfoFromName(CFStringRef name,
333 SecKeyRef* outSigningKey,
334 SecKeyRef* outOctagonSigningKey,
335 SecKeyRef* outOctagonEncryptionKey,
336 CFErrorRef *error)
337 {
338 SOSFullPeerInfoRef result = NULL;
339 SecKeyRef publicKey = NULL;
340 CFDictionaryRef gestalt = NULL;
341
342 *outSigningKey = GeneratePermanentFullECKey(256, name, error);
343
344 *outOctagonSigningKey = GeneratePermanentFullECKey(384, name, error);
345 *outOctagonEncryptionKey = GeneratePermanentFullECKey(384, name, error);
346
347 gestalt = SOSCreatePeerGestaltFromName(name);
348
349 result = SOSFullPeerInfoCreate(NULL, gestalt, NULL, *outSigningKey,
350 *outOctagonSigningKey, *outOctagonEncryptionKey,
351 error);
352
353 CFReleaseNull(gestalt);
354 CFReleaseNull(publicKey);
355
356 return result;
357 }
358 - (NSMutableArray<NSData *>*) SOSPiggyICloudIdentities
359 {
360 SecKeyRef signingKey = NULL;
361 SecKeyRef octagonSigningKey = NULL;
362 SecKeyRef octagonEncryptionKey = NULL;
363 NSMutableArray<NSData *>* icloudidentities = [NSMutableArray array];
364
365 SOSFullPeerInfoRef fpi = SOSCreateFullPeerInfoFromName(CFSTR("Test Peer"), &signingKey, &octagonSigningKey, &octagonEncryptionKey, NULL);
366
367 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(SOSFullPeerInfoGetPeerInfo(fpi), NULL));
368 if (data)
369 [icloudidentities addObject:data];
370
371 CFReleaseNull(signingKey);
372 CFReleaseNull(octagonSigningKey);
373 CFReleaseNull(octagonEncryptionKey);
374
375 return icloudidentities;
376 }
377 static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
378 {
379 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
380 kPIUserDefinedDeviceNameKey, name,
381 NULL);
382 }
383 -(NSMutableDictionary*)SOSPiggyBackCopyFromKeychain
384 {
385 __block NSMutableDictionary *piggybackdata = [[NSMutableDictionary alloc] init];
386 __block CFErrorRef cferror = NULL;
387 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
388 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteSOSTransactionType, &cferror, ^bool {
389 piggybackdata[@"idents"] = [self SOSPiggyICloudIdentities];
390 piggybackdata[@"tlk"] = SOSAccountGetAllTLKs();
391
392 return true;
393 });
394 return ok;
395 });
396
397 XCTAssertNil( (__bridge NSError*)cferror, @"no error with transaction");
398 CFReleaseNull(cferror);
399 return piggybackdata;
400 }
401 - (void)SOSPiggyBackAddToKeychain:(NSDictionary*)piggydata{
402 __block CFErrorRef cferror = NULL;
403 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
404 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteSOSTransactionType, &cferror, ^bool {
405 NSError* error = nil;
406 NSArray* icloudidentities = piggydata[@"idents"];
407 NSArray* tlk = piggydata[@"tlk"];
408
409 SOSPiggyBackAddToKeychain(icloudidentities, tlk);
410
411 XCTAssertNil(error, @"Saved TLK-piggy material to keychain");
412 return true;
413 });
414 return ok;
415 });
416
417 XCTAssertNil( (__bridge NSError*)cferror, @"no error with transaction");
418 CFReleaseNull(cferror);
419 }
420
421 - (void)saveTLKMaterialToKeychain: (CKRecordZoneID*)zoneID {
422 NSError* error = nil;
423 XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
424
425 // Don't make the stashed local copy of the TLK
426 [self.keys[zoneID].tlk saveKeyMaterialToKeychain:false error:&error];
427 XCTAssertNil(error, @"Saved TLK material to keychain");
428 }
429
430 - (void)deleteTLKMaterialFromKeychain: (CKRecordZoneID*)zoneID {
431 NSError* error = nil;
432 XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
433 [self.keys[zoneID].tlk deleteKeyMaterialFromKeychain:&error];
434 XCTAssertNil(error, @"Saved TLK material to keychain");
435 }
436
437 - (void)saveClassKeyMaterialToKeychain: (CKRecordZoneID*)zoneID {
438 NSError* error = nil;
439 XCTAssertNotNil(self.keys[zoneID].classA, "Have a Class A to save for zone %@", zoneID);
440 [self.keys[zoneID].classA saveKeyMaterialToKeychain:&error];
441 XCTAssertNil(error, @"Saved Class A material to keychain");
442
443 XCTAssertNotNil(self.keys[zoneID].classC, "Have a Class C to save for zone %@", zoneID);
444 [self.keys[zoneID].classC saveKeyMaterialToKeychain:&error];
445 XCTAssertNil(error, @"Saved Class C material to keychain");
446 }
447
448
449 - (void)putTLKShareInCloudKit:(CKKSKey*)key
450 from:(CKKSSOSSelfPeer*)sharingPeer
451 to:(id<CKKSPeer>)receivingPeer
452 zoneID:(CKRecordZoneID*)zoneID
453 {
454 NSError* error = nil;
455 CKKSTLKShare* share = [CKKSTLKShare share:key
456 as:sharingPeer
457 to:receivingPeer
458 epoch:-1
459 poisoned:0
460 error:&error];
461 XCTAssertNil(error, "Should have been no error sharing a CKKSKey");
462 XCTAssertNotNil(share, "Should be able to create a share");
463
464 CKRecord* shareRecord = [share CKRecordWithZoneID: zoneID];
465 XCTAssertNotNil(shareRecord, "Should have been able to create a CKRecord");
466
467 FakeCKZone* zone = self.zones[zoneID];
468 XCTAssertNotNil(zone, "Should have a zone to put a TLKShare in");
469 [zone addToZone:shareRecord];
470
471 ZoneKeys* keys = self.keys[zoneID];
472 XCTAssertNotNil(keys, "Have a zonekeys object for this zone");
473 keys.tlkShares = keys.tlkShares ? [keys.tlkShares arrayByAddingObject:share] : @[share];
474 }
475
476 - (void)putTLKSharesInCloudKit:(CKKSKey*)key
477 from:(CKKSSOSSelfPeer*)sharingPeer
478 zoneID:(CKRecordZoneID*)zoneID
479 {
480 NSSet* peers = [self.currentPeers setByAddingObject:self.currentSelfPeer];
481
482 for(id<CKKSPeer> peer in peers) {
483 [self putTLKShareInCloudKit:key from:sharingPeer to:peer zoneID:zoneID];
484 }
485 }
486
487 - (void)putSelfTLKSharesInCloudKit:(CKRecordZoneID*)zoneID {
488 CKKSKey* tlk = self.keys[zoneID].tlk;
489 XCTAssertNotNil(tlk, "Should have a TLK for zone %@", zoneID);
490 [self putTLKSharesInCloudKit:tlk from:self.currentSelfPeer zoneID:zoneID];
491 }
492
493 - (void)saveTLKSharesInLocalDatabase:(CKRecordZoneID*)zoneID {
494 ZoneKeys* keys = self.keys[zoneID];
495 XCTAssertNotNil(keys, "Have a zonekeys object for this zone");
496
497 for(CKKSTLKShare* share in keys.tlkShares) {
498 NSError* error = nil;
499 [share saveToDatabase:&error];
500 XCTAssertNil(error, "Shouldn't have been an error saving a TLKShare to the database");
501 }
502 }
503
504 - (void)createAndSaveFakeKeyHierarchy: (CKRecordZoneID*)zoneID {
505 // Put in CloudKit first, so the records on-disk will have the right change tags
506 [self putFakeKeyHierarchyInCloudKit: zoneID];
507 [self saveFakeKeyHierarchyToLocalDatabase: zoneID];
508 [self saveTLKMaterialToKeychain: zoneID];
509 [self saveClassKeyMaterialToKeychain: zoneID];
510
511 [self putSelfTLKSharesInCloudKit:zoneID];
512 [self saveTLKSharesInLocalDatabase:zoneID];
513 }
514
515 // Override our base class here:
516
517 - (void)expectCKModifyRecords:(NSDictionary<NSString*, NSNumber*>*)expectedRecordTypeCounts
518 deletedRecordTypeCounts:(NSDictionary<NSString*, NSNumber*>*)expectedDeletedRecordTypeCounts
519 zoneID:(CKRecordZoneID*)zoneID
520 checkModifiedRecord:(BOOL (^)(CKRecord*))checkRecord
521 runAfterModification:(void (^) ())afterModification
522 {
523 __weak __typeof(self) weakSelf = self;
524 [super expectCKModifyRecords:expectedRecordTypeCounts
525 deletedRecordTypeCounts:expectedDeletedRecordTypeCounts
526 zoneID:zoneID
527 checkModifiedRecord:checkRecord
528 runAfterModification:^{
529 __strong __typeof(weakSelf) strongSelf = weakSelf;
530 XCTAssertNotNil(strongSelf, "self exists");
531
532 // Reach into our cloudkit database and extract the keys
533 CKRecordID* currentTLKPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassTLK zoneID:zoneID];
534 CKRecordID* currentClassAPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassA zoneID:zoneID];
535 CKRecordID* currentClassCPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassC zoneID:zoneID];
536
537 ZoneKeys* zonekeys = strongSelf.keys[zoneID];
538 if(!zonekeys) {
539 zonekeys = [[ZoneKeys alloc] init];
540 strongSelf.keys[zoneID] = zonekeys;
541 }
542
543 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID], "Have a currentTLKPointer");
544 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID], "Have a currentClassAPointer");
545 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID], "Have a currentClassCPointer");
546 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey], "Have a currentTLKPointer parent");
547 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassAPointer parent");
548 XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassCPointer parent");
549 XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentTLKPointer parent UUID");
550 XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassAPointer parent UUID");
551 XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassCPointer parent UUID");
552
553 zonekeys.currentTLKPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID]];
554 zonekeys.currentClassAPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID]];
555 zonekeys.currentClassCPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID]];
556
557 XCTAssertNotNil(zonekeys.currentTLKPointer.currentKeyUUID, "Have a currentTLKPointer current UUID");
558 XCTAssertNotNil(zonekeys.currentClassAPointer.currentKeyUUID, "Have a currentClassAPointer current UUID");
559 XCTAssertNotNil(zonekeys.currentClassCPointer.currentKeyUUID, "Have a currentClassCPointer current UUID");
560
561 CKRecordID* currentTLKID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentTLKPointer.currentKeyUUID zoneID:zoneID];
562 CKRecordID* currentClassAID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassAPointer.currentKeyUUID zoneID:zoneID];
563 CKRecordID* currentClassCID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassCPointer.currentKeyUUID zoneID:zoneID];
564
565 zonekeys.tlk = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKID]];
566 zonekeys.classA = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAID]];
567 zonekeys.classC = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCID]];
568
569 XCTAssertNotNil(zonekeys.tlk, "Have the current TLK");
570 XCTAssertNotNil(zonekeys.classA, "Have the current Class A key");
571 XCTAssertNotNil(zonekeys.classC, "Have the current Class C key");
572
573 NSMutableArray<CKKSTLKShare*>* shares = [NSMutableArray array];
574 for(CKRecordID* recordID in strongSelf.zones[zoneID].currentDatabase.allKeys) {
575 if([recordID.recordName hasPrefix: [CKKSTLKShare ckrecordPrefix]]) {
576 CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:strongSelf.zones[zoneID].currentDatabase[recordID]];
577 XCTAssertNotNil(share, "Should be able to parse a CKKSTLKShare CKRecord into a CKKSTLKShare");
578 [shares addObject:share];
579 }
580 }
581 zonekeys.tlkShares = shares;
582
583 if(afterModification) {
584 afterModification();
585 }
586 }];
587 }
588
589 - (void)expectCKReceiveSyncKeyHierarchyError:(CKRecordZoneID*)zoneID {
590
591 __weak __typeof(self) weakSelf = self;
592 [[self.mockDatabase expect] addOperation:[OCMArg checkWithBlock:^BOOL(id obj) {
593 __strong __typeof(weakSelf) strongSelf = weakSelf;
594 XCTAssertNotNil(strongSelf, "self exists");
595
596 __block bool rejected = false;
597 if ([obj isKindOfClass:[CKModifyRecordsOperation class]]) {
598 CKModifyRecordsOperation *op = (CKModifyRecordsOperation *)obj;
599
600 if(!op.atomic) {
601 // We only care about atomic operations
602 return NO;
603 }
604
605 // We want to only match zone updates pertaining to this zone
606 for(CKRecord* record in op.recordsToSave) {
607 if(![record.recordID.zoneID isEqual: zoneID]) {
608 return NO;
609 }
610 }
611
612 // we oly want to match updates that are updating a class C or class A CKP
613 bool updatingClassACKP = false;
614 bool updatingClassCCKP = false;
615 for(CKRecord* record in op.recordsToSave) {
616 if([record.recordID.recordName isEqualToString:SecCKKSKeyClassA]) {
617 updatingClassACKP = true;
618 }
619 if([record.recordID.recordName isEqualToString:SecCKKSKeyClassC]) {
620 updatingClassCCKP = true;
621 }
622 }
623
624 if(!updatingClassACKP && !updatingClassCCKP) {
625 return NO;
626 }
627
628 FakeCKZone* zone = strongSelf.zones[zoneID];
629 XCTAssertNotNil(zone, "Should have a zone for these records");
630
631 // We only want to match if the synckeys aren't pointing correctly
632
633 ZoneKeys* zonekeys = [[ZoneKeys alloc] initLoadingRecordsFromZone:zone];
634
635 XCTAssertNotNil(zonekeys.currentTLKPointer, "Have a currentTLKPointer");
636 XCTAssertNotNil(zonekeys.currentClassAPointer, "Have a currentClassAPointer");
637 XCTAssertNotNil(zonekeys.currentClassCPointer, "Have a currentClassCPointer");
638
639 XCTAssertNotNil(zonekeys.tlk, "Have the current TLK");
640 XCTAssertNotNil(zonekeys.classA, "Have the current Class A key");
641 XCTAssertNotNil(zonekeys.classC, "Have the current Class C key");
642
643 // Ensure that either the Class A synckey or the class C synckey do not immediately wrap to the current TLK
644 bool classALinkBroken = ![zonekeys.classA.parentKeyUUID isEqualToString:zonekeys.tlk.uuid];
645 bool classCLinkBroken = ![zonekeys.classC.parentKeyUUID isEqualToString:zonekeys.tlk.uuid];
646
647 // Neither synckey link is broken. Don't match this operation.
648 if(!classALinkBroken && !classCLinkBroken) {
649 return NO;
650 }
651
652 NSMutableDictionary<CKRecordID*, NSError*>* failedRecords = [[NSMutableDictionary alloc] init];
653
654 @synchronized(zone.currentDatabase) {
655 for(CKRecord* record in op.recordsToSave) {
656 if(classALinkBroken && [record.recordID.recordName isEqualToString:SecCKKSKeyClassA]) {
657 failedRecords[record.recordID] = [strongSelf ckInternalServerExtensionError:CKKSServerUnexpectedSyncKeyInChain description:@"synckey record: current classA synckey does not point to current tlk synckey"];
658 rejected = true;
659 }
660 if(classCLinkBroken && [record.recordID.recordName isEqualToString:SecCKKSKeyClassC]) {
661 failedRecords[record.recordID] = [strongSelf ckInternalServerExtensionError:CKKSServerUnexpectedSyncKeyInChain description:@"synckey record: current classC synckey does not point to current tlk synckey"];
662 rejected = true;
663 }
664 }
665 }
666
667 if(rejected) {
668 [strongSelf rejectWrite: op failedRecords:failedRecords];
669 }
670 }
671 return rejected ? YES : NO;
672 }]];
673 }
674
675 - (void)checkNoCKKSData: (CKKSKeychainView*) view {
676 // Test that there are no items in the database
677 [view dispatchSync:^bool{
678 NSError* error = nil;
679 NSArray<CKKSMirrorEntry*>* ckmes = [CKKSMirrorEntry all: view.zoneID error:&error];
680 XCTAssertNil(error, "No error fetching CKMEs");
681 XCTAssertEqual(ckmes.count, 0ul, "No CKMirrorEntries");
682
683 NSArray<CKKSOutgoingQueueEntry*>* oqes = [CKKSOutgoingQueueEntry all: view.zoneID error:&error];
684 XCTAssertNil(error, "No error fetching OQEs");
685 XCTAssertEqual(oqes.count, 0ul, "No OutgoingQueueEntries");
686
687 NSArray<CKKSIncomingQueueEntry*>* iqes = [CKKSIncomingQueueEntry all: view.zoneID error:&error];
688 XCTAssertNil(error, "No error fetching IQEs");
689 XCTAssertEqual(iqes.count, 0ul, "No IncomingQueueEntries");
690
691 NSArray<CKKSKey*>* keys = [CKKSKey all: view.zoneID error:&error];
692 XCTAssertNil(error, "No error fetching keys");
693 XCTAssertEqual(keys.count, 0ul, "No CKKSKeys");
694
695 NSArray<CKKSDeviceStateEntry*>* deviceStates = [CKKSDeviceStateEntry allInZone:view.zoneID error:&error];
696 XCTAssertNil(error, "should be no error fetching device states");
697 XCTAssertEqual(deviceStates.count, 0ul, "No Device State entries");
698 return false;
699 }];
700 }
701
702 - (BOOL (^) (CKRecord*)) checkClassABlock: (CKRecordZoneID*) zoneID message:(NSString*) message {
703 __weak __typeof(self) weakSelf = self;
704 return ^BOOL(CKRecord* record) {
705 __strong __typeof(weakSelf) strongSelf = weakSelf;
706 XCTAssertNotNil(strongSelf, "self exists");
707
708 ZoneKeys* zoneKeys = strongSelf.keys[zoneID];
709 XCTAssertNotNil(zoneKeys, "Have zone keys for %@", zoneID);
710 XCTAssertEqualObjects([record[SecCKRecordParentKeyRefKey] recordID].recordName, zoneKeys.classA.uuid, "%@", message);
711 return [[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqual: zoneKeys.classA.uuid];
712 };
713 }
714
715 - (BOOL (^) (CKRecord*)) checkClassCBlock: (CKRecordZoneID*) zoneID message:(NSString*) message {
716 __weak __typeof(self) weakSelf = self;
717 return ^BOOL(CKRecord* record) {
718 __strong __typeof(weakSelf) strongSelf = weakSelf;
719 XCTAssertNotNil(strongSelf, "self exists");
720
721 ZoneKeys* zoneKeys = strongSelf.keys[zoneID];
722 XCTAssertNotNil(zoneKeys, "Have zone keys for %@", zoneID);
723 XCTAssertEqualObjects([record[SecCKRecordParentKeyRefKey] recordID].recordName, zoneKeys.classC.uuid, "%@", message);
724 return [[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqual: zoneKeys.classC.uuid];
725 };
726 }
727
728 - (BOOL (^) (CKRecord*)) checkPasswordBlock:(CKRecordZoneID*)zoneID
729 account:(NSString*)account
730 password:(NSString*)password {
731 __weak __typeof(self) weakSelf = self;
732 return ^BOOL(CKRecord* record) {
733 __strong __typeof(weakSelf) strongSelf = weakSelf;
734 XCTAssertNotNil(strongSelf, "self exists");
735
736 ZoneKeys* zoneKeys = strongSelf.keys[zoneID];
737 XCTAssertNotNil(zoneKeys, "Have zone keys for %@", zoneID);
738 XCTAssertNotNil([record[SecCKRecordParentKeyRefKey] recordID].recordName, "Have a wrapping key");
739
740 CKKSKey* key = nil;
741 if([[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqualToString: zoneKeys.classC.uuid]) {
742 key = zoneKeys.classC;
743 } else if([[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqualToString: zoneKeys.classA.uuid]) {
744 key = zoneKeys.classA;
745 }
746 XCTAssertNotNil(key, "Found a key via UUID");
747
748 CKKSMirrorEntry* ckme = [[CKKSMirrorEntry alloc] initWithCKRecord: record];
749
750 NSError* error = nil;
751 NSDictionary* dict = [CKKSItemEncrypter decryptItemToDictionary:ckme.item error:&error];
752 XCTAssertNil(error, "No error decrypting item");
753 XCTAssertEqualObjects(account, dict[(id)kSecAttrAccount], "Account matches");
754 XCTAssertEqualObjects([password dataUsingEncoding:NSUTF8StringEncoding], dict[(id)kSecValueData], "Password matches");
755 return YES;
756 };
757 }
758
759 - (NSDictionary*)fakeRecordDictionary:(NSString*) account zoneID:(CKRecordZoneID*)zoneID {
760 NSError* error = nil;
761
762 /* Basically: @{
763 @"acct" : @"account-delete-me",
764 @"agrp" : @"com.apple.security.sos",
765 @"cdat" : @"2016-12-21 03:33:25 +0000",
766 @"class" : @"genp",
767 @"mdat" : @"2016-12-21 03:33:25 +0000",
768 @"musr" : [[NSData alloc] init],
769 @"pdmn" : @"ak",
770 @"sha1" : [[NSData alloc] initWithBase64EncodedString: @"C3VWONaOIj8YgJjk/xwku4By1CY=" options:0],
771 @"svce" : @"",
772 @"tomb" : [NSNumber numberWithInt: 0],
773 @"v_Data" : [@"data" dataUsingEncoding: NSUTF8StringEncoding],
774 };
775 TODO: this should be binary encoded instead of expanded, but the plist encoder should handle it fine */
776 NSData* itemdata = [[NSData alloc] initWithBase64EncodedString:@"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YWNjdDwva2V5PgoJPHN0cmluZz5hY2NvdW50LWRlbGV0ZS1tZTwvc3RyaW5nPgoJPGtleT5hZ3JwPC9rZXk+Cgk8c3RyaW5nPmNvbS5hcHBsZS5zZWN1cml0eS5zb3M8L3N0cmluZz4KCTxrZXk+Y2RhdDwva2V5PgoJPGRhdGU+MjAxNi0xMi0yMVQwMzozMzoyNVo8L2RhdGU+Cgk8a2V5PmNsYXNzPC9rZXk+Cgk8c3RyaW5nPmdlbnA8L3N0cmluZz4KCTxrZXk+bWRhdDwva2V5PgoJPGRhdGU+MjAxNi0xMi0yMVQwMzozMzoyNVo8L2RhdGU+Cgk8a2V5Pm11c3I8L2tleT4KCTxkYXRhPgoJPC9kYXRhPgoJPGtleT5wZG1uPC9rZXk+Cgk8c3RyaW5nPmFrPC9zdHJpbmc+Cgk8a2V5PnNoYTE8L2tleT4KCTxkYXRhPgoJQzNWV09OYU9JajhZZ0pqay94d2t1NEJ5MUNZPQoJPC9kYXRhPgoJPGtleT5zdmNlPC9rZXk+Cgk8c3RyaW5nPjwvc3RyaW5nPgoJPGtleT50b21iPC9rZXk+Cgk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJPGtleT52X0RhdGE8L2tleT4KCTxkYXRhPgoJWkdGMFlRPT0KCTwvZGF0YT4KPC9kaWN0Pgo8L3BsaXN0Pgo=" options:0];
777 NSMutableDictionary * item = [[NSPropertyListSerialization propertyListWithData:itemdata
778 options:0
779 format:nil
780 error:&error] mutableCopy];
781 // Fix up dictionary
782 item[@"agrp"] = @"com.apple.security.ckks";
783 item[@"vwht"] = @"keychain";
784 XCTAssertNil(error, "no error interpreting data as item");
785 XCTAssertNotNil(item, "interpreted data as item");
786
787 if(zoneID && ![zoneID.zoneName isEqualToString:@"keychain"]) {
788 [item setObject: zoneID.zoneName forKey: (__bridge id) kSecAttrSyncViewHint];
789 }
790
791 if(account) {
792 [item setObject: account forKey: (__bridge id) kSecAttrAccount];
793 }
794 return item;
795 }
796
797 - (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName {
798 return [self createFakeRecord: zoneID recordName:recordName withAccount: nil key:nil];
799 }
800
801 - (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount: (NSString*) account {
802 return [self createFakeRecord: zoneID recordName:recordName withAccount:account key:nil];
803 }
804
805 - (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount: (NSString*) account key:(CKKSKey*)key {
806 NSDictionary* item = [self fakeRecordDictionary: account zoneID:zoneID];
807
808 CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:recordName zoneID:zoneID];
809 if(key) {
810 return [self newRecord: ckrid withNewItemData:item key:key];
811 } else {
812 return [self newRecord: ckrid withNewItemData:item];
813 }
814 }
815
816 - (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary {
817 ZoneKeys* zonekeys = self.keys[recordID.zoneID];
818 XCTAssertNotNil(zonekeys, "Have zone keys for zone");
819 XCTAssertNotNil(zonekeys.classC, "Have class C key for zone");
820
821 return [self newRecord:recordID withNewItemData:dictionary key:zonekeys.classC];
822 }
823
824 - (CKKSItem*)newItem:(CKRecordID*)recordID withNewItemData:(NSDictionary*)dictionary key:(CKKSKey*)key {
825 NSError* error = nil;
826 CKKSItem* cipheritem = [CKKSItemEncrypter encryptCKKSItem:[[CKKSItem alloc] initWithUUID:recordID.recordName
827 parentKeyUUID:key.uuid
828 zoneID:recordID.zoneID]
829 dataDictionary:dictionary
830 updatingCKKSItem:nil
831 parentkey:key
832 error:&error];
833 XCTAssertNil(error, "encrypted item with class c key");
834 XCTAssertNotNil(cipheritem, "Have an encrypted item");
835
836 return cipheritem;
837 }
838
839 - (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary key:(CKKSKey*)key {
840 CKKSItem* item = [self newItem:recordID withNewItemData:dictionary key:key];
841
842 CKRecord* ckr = [item CKRecordWithZoneID: recordID.zoneID];
843 XCTAssertNotNil(ckr, "Created a CKRecord");
844 return ckr;
845 }
846
847 - (NSDictionary*)decryptRecord: (CKRecord*) record {
848 CKKSItem* item = [[CKKSItem alloc] initWithCKRecord: record];
849
850 NSError* error = nil;
851
852 NSDictionary* ret = [CKKSItemEncrypter decryptItemToDictionary: item error:&error];
853 XCTAssertNil(error);
854 XCTAssertNotNil(ret);
855 return ret;
856 }
857
858 - (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint: (NSString*) viewHint access: (NSString*) access expecting: (OSStatus) status message: (NSString*) message {
859 NSMutableDictionary* query = [@{
860 (id)kSecClass : (id)kSecClassGenericPassword,
861 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
862 (id)kSecAttrAccessible: access,
863 (id)kSecAttrAccount : account,
864 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
865 (id)kSecValueData : (id) [password dataUsingEncoding:NSUTF8StringEncoding],
866 } mutableCopy];
867
868 if(viewHint) {
869 query[(id)kSecAttrSyncViewHint] = viewHint;
870 } else {
871 // Fake it as 'keychain'. This lets CKKSScanLocalItemsOperation for the test-only 'keychain' view find items which would normally not have a view hint.
872 query[(id)kSecAttrSyncViewHint] = @"keychain";
873 }
874
875 XCTAssertEqual(status, SecItemAdd((__bridge CFDictionaryRef) query, NULL), @"%@", message);
876 }
877
878 - (void)addGenericPassword: (NSString*) password account: (NSString*) account expecting: (OSStatus) status message: (NSString*) message {
879 [self addGenericPassword:password account:account viewHint:nil access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:message];
880
881 }
882
883 - (void)addGenericPassword: (NSString*) password account: (NSString*) account {
884 [self addGenericPassword:password account:account viewHint:nil access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:@"Add item to keychain"];
885 }
886
887 - (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint:(NSString*)viewHint {
888 [self addGenericPassword:password account:account viewHint:viewHint access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:@"Add item to keychain with a viewhint"];
889 }
890
891 - (void)updateGenericPassword: (NSString*) newPassword account: (NSString*)account {
892 NSDictionary* query = @{
893 (id)kSecClass : (id)kSecClassGenericPassword,
894 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
895 (id)kSecAttrAccount : account,
896 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
897 };
898
899 NSDictionary* update = @{
900 (id)kSecValueData : (id) [newPassword dataUsingEncoding:NSUTF8StringEncoding],
901 };
902 XCTAssertEqual(errSecSuccess, SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update), @"Updating item %@ to %@", account, newPassword);
903 }
904
905 - (void)updateAccountOfGenericPassword:(NSString*)newAccount
906 account:(NSString*)account {
907 NSDictionary* query = @{
908 (id)kSecClass : (id)kSecClassGenericPassword,
909 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
910 (id)kSecAttrAccount : account,
911 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
912 };
913
914 NSDictionary* update = @{
915 (id)kSecAttrAccount : (id) newAccount,
916 };
917 XCTAssertEqual(errSecSuccess, SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update), @"Updating item %@ to account %@", account, newAccount);
918 }
919
920 - (void)deleteGenericPassword: (NSString*) account {
921 NSDictionary* query = @{
922 (id)kSecClass : (id)kSecClassGenericPassword,
923 (id)kSecAttrAccount : account,
924 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
925 };
926
927 XCTAssertEqual(errSecSuccess, SecItemDelete((__bridge CFDictionaryRef) query), @"Deleting item %@", account);
928 }
929
930 - (void)findGenericPassword: (NSString*) account expecting: (OSStatus) status {
931 NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword,
932 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
933 (id)kSecAttrAccount : account,
934 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
935 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
936 };
937 XCTAssertEqual(status, SecItemCopyMatching((__bridge CFDictionaryRef) query, NULL), "Finding item %@", account);
938 }
939
940 - (void)checkGenericPassword: (NSString*) password account: (NSString*) account {
941 NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword,
942 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
943 (id)kSecAttrAccount : account,
944 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
945 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
946 (id)kSecReturnData : (id)kCFBooleanTrue,
947 };
948 CFTypeRef result = NULL;
949
950 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef) query, &result), "Finding item %@", account);
951 XCTAssertNotNil((__bridge id)result, "Received an item");
952
953 NSString* storedPassword = [[NSString alloc] initWithData: (__bridge NSData*) result encoding: NSUTF8StringEncoding];
954 XCTAssertNotNil(storedPassword, "Password parsed as a password");
955
956 XCTAssertEqualObjects(storedPassword, password, "Stored password matches received password");
957 }
958
959 -(XCTestExpectation*)expectChangeForView:(NSString*)view {
960 NSString* notification = [NSString stringWithFormat: @"com.apple.security.view-change.%@", view];
961 return [self expectationForNotification:notification object:nil handler:^BOOL(NSNotification * _Nonnull nsnotification) {
962 secnotice("ckks", "Got a notification for %@: %@", notification, nsnotification);
963 return YES;
964 }];
965 }
966
967 - (void)expectCKKSTLKSelfShareUpload:(CKRecordZoneID*)zoneID {
968 [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:zoneID];
969 }
970
971 - (void)checkNSyncableTLKsInKeychain:(size_t)n {
972 NSDictionary *query = @{(id)kSecClass : (id)kSecClassInternetPassword,
973 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
974 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
975 (id)kSecAttrDescription: SecCKKSKeyClassTLK,
976 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
977 (id)kSecReturnAttributes : (id)kCFBooleanTrue,
978 };
979 CFTypeRef result = NULL;
980
981 if(n == 0) {
982 XCTAssertEqual(errSecItemNotFound, SecItemCopyMatching((__bridge CFDictionaryRef) query, &result), "Should have found no TLKs");
983 } else {
984 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef) query, &result), "Should have found TLKs");
985 NSArray* items = (NSArray*) CFBridgingRelease(result);
986
987 XCTAssertEqual(items.count, n, "Should have received %lu items", n);
988 }
989 }
990
991 @end
992
993 #endif // OCTAGON