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