]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/Regressions/SOSAccountTesting.h
Security-59306.120.7.tar.gz
[apple/security.git] / keychain / securityd / Regressions / SOSAccountTesting.h
1 /*
2 * Copyright (c) 2013-2014 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
25 #ifndef SEC_SOSAccountTesting_h
26 #define SEC_SOSAccountTesting_h
27
28 #include <CoreFoundation/CoreFoundation.h>
29 #include "keychain/SecureObjectSync/SOSAccountPriv.h"
30 #include "keychain/SecureObjectSync/SOSTransport.h"
31 #include "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
32 #include <Security/SecureObjectSync/SOSPeerInfo.h>
33 #include "keychain/SecureObjectSync/SOSPeerInfoV2.h"
34 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
35 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
36 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
37 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
38
39 #include "SOSTestDataSource.h"
40 #include "SOSRegressionUtilities.h"
41
42 #include "SOSTransportTestTransports.h"
43 #include "testmore.h"
44 #include <utilities/SecCFWrappers.h>
45 //
46 // Implicit transaction helpers
47 //
48
49 static inline bool SOSAccountResetToOffering_wTxn(SOSAccount* acct, CFErrorRef* error)
50 {
51 __block bool result = false;
52 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
53 SecKeyRef user_key = SOSAccountGetPrivateCredential(txn.account, error);
54 if (!user_key)
55 return;
56 result = [acct.trust resetToOffering:txn key:user_key err:error];
57 }];
58 return result;
59 }
60
61 static inline bool SOSAccountJoinCirclesAfterRestore_wTxn(SOSAccount* acct, CFErrorRef* error)
62 {
63 __block bool result = false;
64 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
65 result = SOSAccountJoinCirclesAfterRestore(txn, nil, error);
66 }];
67 return result;
68 }
69
70 static inline bool SOSAccountJoinCircles_wTxn(SOSAccount* acct, CFErrorRef* error)
71 {
72 __block bool result = false;
73 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
74 result = SOSAccountJoinCircles(txn, nil, error);
75 }];
76 return result;
77 }
78
79 static inline bool SOSAccountCheckHasBeenInSync_wTxn(SOSAccount* account)
80 {
81 return SOSAccountHasCompletedInitialSync(account);
82 }
83
84 static inline void SOSAccountPeerGotInSync_wTxn(SOSAccount* acct, SOSPeerInfoRef peer)
85 {
86 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
87 CFMutableSetRef views = SOSPeerInfoCopyEnabledViews(peer);
88 SOSAccountPeerGotInSync(txn, SOSPeerInfoGetPeerID(peer), views);
89 CFReleaseNull(views);
90 }];
91 }
92
93 static inline bool SOSAccountSetBackupPublicKey_wTxn(SOSAccount* acct, CFDataRef backupKey, CFErrorRef* error)
94 {
95 __block bool result = false;
96 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
97 result = SOSAccountSetBackupPublicKey(txn, backupKey, error);
98 }];
99 return result;
100 }
101
102 static inline bool SOSAccountRemoveBackupPublickey_wTxn(SOSAccount* acct, CFErrorRef* error)
103 {
104 __block bool result = false;
105 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
106 result = SOSAccountRemoveBackupPublickey(txn, error);
107 }];
108 return result;
109 }
110
111 static inline SOSViewResultCode SOSAccountUpdateView_wTxn(SOSAccount* acct, CFStringRef viewname, SOSViewActionCode actionCode, CFErrorRef *error) {
112 __block SOSViewResultCode result = false;
113 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
114 result = [acct.trust updateView:acct name:viewname code:actionCode err:error];
115 }];
116 return result;
117 }
118
119 static inline bool SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(SOSAccount *account, CFStringRef viewname) {
120 __block bool result = false;
121 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
122 result = SOSAccountIsMyPeerInBackupAndCurrentInView(account, viewname);
123 }];
124 return result;
125 }
126
127 static inline bool SOSAccountIsPeerInBackupAndCurrentInView_wTxn(SOSAccount *account, SOSPeerInfoRef peerInfo, CFStringRef viewname) {
128 __block bool result = false;
129 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
130 result = SOSAccountIsPeerInBackupAndCurrentInView(account, peerInfo, viewname);
131 }];
132 return result;
133 }
134
135 static inline bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(SOSAccount *account, CFStringRef viewname) {
136 __block bool result = false;
137 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
138 result = SOSAccountRecoveryKeyIsInBackupAndCurrentInView(account, viewname);
139 }];
140 return result;
141 }
142
143 static inline SOSBackupSliceKeyBagRef SOSAccountBackupSliceKeyBagForView_wTxn(SOSAccount *account, CFStringRef viewname, CFErrorRef *error) {
144 __block SOSBackupSliceKeyBagRef result = NULL;
145 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
146 result = SOSAccountBackupSliceKeyBagForView(account, viewname, error);
147 }];
148 return result;
149 }
150
151
152
153
154 //
155 // Account comparison
156 //
157
158 #define kAccountsAgreeTestMin 9
159 #define kAccountsAgreeTestPerPeer 1
160 #define accountsAgree(x) (kAccountsAgreeTestMin + kAccountsAgreeTestPerPeer * (x))
161
162 static void SOSAccountResetToTest(SOSAccount* a, CFStringRef accountName) {
163 SOSUnregisterTransportKeyParameter(a.key_transport);
164 SOSUnregisterTransportCircle((SOSCircleStorageTransport*)a.circle_transport);
165 SOSUnregisterTransportMessage((SOSMessage*)a.kvs_message_transport);
166
167 if(key_transports)
168 CFArrayRemoveAllValue(key_transports, (__bridge CFTypeRef)(a.key_transport));
169 if(message_transports){
170 CFArrayRemoveAllValue(message_transports, (__bridge CFTypeRef)a.kvs_message_transport);
171 }
172 if(circle_transports)
173 CFArrayRemoveAllValue(circle_transports, (__bridge CFTypeRef)a.circle_transport);
174
175 a.circle_transport = nil;
176 a.key_transport = nil;
177 a.kvs_message_transport = nil;
178
179 [a performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
180 SOSAccountEnsureFactoryCirclesTest(a, accountName);
181 }];
182 }
183
184
185 static SOSAccount* SOSAccountCreateBasicTest(CFAllocatorRef allocator,
186 CFStringRef accountName,
187 CFDictionaryRef gestalt,
188 SOSDataSourceFactoryRef factory) {
189 SOSAccount* a;
190 a = SOSAccountCreate(kCFAllocatorDefault, gestalt, factory);
191
192 return a;
193 }
194
195 static SOSAccount* SOSAccountCreateTest(CFAllocatorRef allocator,
196 CFStringRef accountName,
197 CFDictionaryRef gestalt,
198 SOSDataSourceFactoryRef factory) {
199 SOSAccount* a = SOSAccountCreateBasicTest(allocator, accountName, gestalt, factory);
200
201 SOSAccountResetToTest(a, accountName);
202 if(a)
203 SOSAccountInflateTestTransportsForCircle(a, SOSCircleGetName([a.trust getCircle:NULL]), accountName, NULL);
204 return a;
205 }
206
207 static SOSAccount* SOSAccountCreateTestFromData(CFAllocatorRef allocator,
208 CFDataRef data,
209 CFStringRef accountName,
210 SOSDataSourceFactoryRef factory) {
211 SOSAccount* a = [SOSAccount accountFromData:(__bridge NSData*) data
212 factory:factory
213 error:nil];
214 if (!a) {
215 CFDictionaryRef gestalt = SOSCreatePeerGestaltFromName(accountName);
216 a = SOSAccountCreate(allocator, gestalt, factory);
217 CFReleaseNull(gestalt);
218 }
219
220 SOSAccountResetToTest(a, accountName);
221 if(a)
222 SOSAccountInflateTestTransportsForCircle(a, SOSCircleGetName([a.trust getCircle:NULL]), accountName, NULL);
223
224 return a;
225 }
226
227
228 static inline bool SOSAccountAssertUserCredentialsAndUpdate(SOSAccount* account,
229 CFStringRef user_account, CFDataRef user_password,
230 CFErrorRef *error)
231 {
232 bool success = false;
233 success = SOSAccountAssertUserCredentials(account, user_account, user_password, error);
234 require_quiet(success, done);
235
236 success = SOSAccountGenerationSignatureUpdate(account, error);
237
238 done:
239 return success;
240 }
241
242
243
244 static void unretired_peers_is_subset(const char* label, CFArrayRef peers, CFSetRef allowed_peers)
245 {
246 CFArrayForEach(peers, ^(const void *value) {
247 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
248
249 CFErrorRef leftError = NULL;
250 CFErrorRef rightError = NULL;
251
252 ok(SOSPeerInfoIsRetirementTicket(pi) || SOSPeerInfoIsCloudIdentity(pi) || CFSetContainsValue(allowed_peers, pi), "Peer is allowed (%s) Peer: %@, Allowed %@", label, pi, allowed_peers);
253
254 CFReleaseNull(leftError);
255 CFReleaseNull(rightError);
256 });
257 }
258
259 static void accounts_agree_internal(char *label, SOSAccount* left, SOSAccount* right, bool check_peers)
260 {
261 CFErrorRef error = NULL;
262 {
263 CFArrayRef leftPeers = SOSAccountCopyActivePeers(left, &error);
264 ok(leftPeers, "Left peers (%@) - %s", error, label);
265 CFReleaseNull(error);
266
267 CFArrayRef rightPeers = SOSAccountCopyActivePeers(right, &error);
268 ok(rightPeers, "Right peers (%@) - %s", error, label);
269 CFReleaseNull(error);
270
271 ok(CFEqual(leftPeers, rightPeers), "Matching peers (%s) Left: %@, Right: %@", label, leftPeers, rightPeers);
272
273 if (check_peers) {
274 CFMutableSetRef allowed_identities = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
275
276 SOSFullPeerInfoRef leftFullPeer = [left.trust CopyAccountIdentityPeerInfo];
277
278 if (leftFullPeer)
279 CFSetAddValue(allowed_identities, SOSFullPeerInfoGetPeerInfo(leftFullPeer));
280 CFReleaseNull(leftFullPeer);
281
282 SOSFullPeerInfoRef rightFullPeer = [right.trust CopyAccountIdentityPeerInfo];
283
284 if (rightFullPeer)
285 CFSetAddValue(allowed_identities, SOSFullPeerInfoGetPeerInfo(rightFullPeer));
286 CFReleaseNull(rightFullPeer);
287
288 unretired_peers_is_subset(label, leftPeers, allowed_identities);
289
290 CFReleaseNull(allowed_identities);
291 }
292
293 CFReleaseNull(leftPeers);
294 CFReleaseNull(rightPeers);
295 }
296 {
297 CFArrayRef leftConcurringPeers = SOSAccountCopyConcurringPeers(left, &error);
298 ok(leftConcurringPeers, "Left peers (%@) - %s", error, label);
299
300 CFArrayRef rightConcurringPeers = SOSAccountCopyConcurringPeers(right, &error);
301 ok(rightConcurringPeers, "Right peers (%@) - %s", error, label);
302
303 ok(CFEqual(leftConcurringPeers, rightConcurringPeers), "Matching concurring peers Left: %@, Right: %@", leftConcurringPeers, rightConcurringPeers);
304
305 CFReleaseNull(leftConcurringPeers);
306 CFReleaseNull(rightConcurringPeers);
307 }
308 {
309 CFArrayRef leftApplicants = SOSAccountCopyApplicants(left, &error);
310 ok(leftApplicants, "Left Applicants (%@) - %s", error, label);
311
312 CFArrayRef rightApplicants = SOSAccountCopyApplicants(right, &error);
313 ok(rightApplicants, "Left Applicants (%@) - %s", error, label);
314
315 ok(CFEqual(leftApplicants, rightApplicants), "Matching applicants (%s) Left: %@, Right: %@", label, leftApplicants, rightApplicants);
316
317 CFReleaseNull(leftApplicants);
318 CFReleaseNull(rightApplicants);
319 }
320 }
321
322 static inline void accounts_agree(char *label, SOSAccount* left, SOSAccount* right)
323 {
324 accounts_agree_internal(label, left, right, true);
325 }
326
327
328 //
329 // Change handling
330 //
331
332 static inline CFStringRef CFArrayCopyCompactDescription(CFArrayRef array) {
333 if (!isArray(array))
334 return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<Not an array! %@>"), array);
335
336 CFMutableStringRef result = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
337
338 __block CFStringRef separator = CFSTR("");
339 CFArrayForEach(array, ^(const void *value) {
340 CFStringAppendFormat(result, NULL, CFSTR("%@%@"), separator, value);
341 separator = CFSTR(",");
342 });
343
344 CFStringAppend(result, CFSTR("]"));
345
346 CFReleaseSafe(separator);
347
348 return result;
349 }
350
351 static inline CFStringRef SOSAccountCopyName(SOSAccount* account) {
352 SOSPeerInfoRef pi = account.peerInfo;
353
354 return pi ? CFStringCreateCopy(kCFAllocatorDefault, SOSPeerInfoGetPeerName(pi)) : CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@"), account);
355 }
356
357 static inline CFStringRef CopyChangesDescription(CFDictionaryRef changes) {
358
359 CFStringRef pendingChanges = CFDictionaryCopyCompactDescription((CFDictionaryRef) CFDictionaryGetValue(changes, kCFNull));
360
361 CFMutableStringRef peerTable = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
362
363 __block CFStringRef separator = CFSTR("");
364
365 CFDictionaryForEach(changes, ^(const void *key, const void *value) {
366 if (CFGetTypeID(key) == SOSAccountGetTypeID()) {
367 CFStringRef accountName = SOSAccountCopyName((__bridge SOSAccount*)key);
368 CFStringRef arrayDescription = CFArrayCopyCompactDescription(value);
369
370 CFStringAppendFormat(peerTable, NULL, CFSTR("%@%@:%@"), separator, accountName, arrayDescription);
371 separator = CFSTR(", ");
372
373 CFReleaseSafe(accountName);
374 CFReleaseSafe(arrayDescription);
375 }
376 });
377
378 CFStringAppend(peerTable, CFSTR("]"));
379
380 CFStringRef result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<TestChanges %@ %@>"), pendingChanges, peerTable);
381 CFReleaseNull(pendingChanges);
382 CFReleaseNull(peerTable);
383
384 return result;
385 };
386
387 static void CFDictionaryOverlayDictionary(CFMutableDictionaryRef target, CFMutableDictionaryRef overlay) {
388 CFMutableSetRef keysToRemove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
389
390 CFDictionaryForEach(overlay, ^(const void *key, const void *value) {
391 const void *current_value = CFDictionaryGetValue(target, key);
392 if (CFEqualSafe(current_value, value) || (isNull(value) && current_value == NULL)) {
393 CFSetAddValue(keysToRemove, key);
394 } else {
395 CFDictionarySetValue(target, key, value);
396 }
397 });
398
399 CFSetForEach(keysToRemove, ^(const void *value) {
400 CFDictionaryRemoveValue(overlay, value);
401 });
402
403 CFReleaseNull(keysToRemove);
404 }
405
406 static void CFArrayAppendKeys(CFMutableArrayRef keys, CFDictionaryRef newKeysToAdd) {
407 CFDictionaryForEach(newKeysToAdd, ^(const void *key, const void *value) {
408 CFArrayAppendValue(keys, key);
409 });
410 }
411
412 static bool AddNewChanges(CFMutableDictionaryRef changesRecord, CFMutableDictionaryRef newKeysAndValues, SOSAccount* sender)
413 {
414 __block bool changes_added = false;
415 CFMutableDictionaryRef emptyDictionary = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
416 CFDictionaryAddValue(changesRecord, kCFNull, emptyDictionary);
417 CFReleaseNull(emptyDictionary);
418
419 CFDictionaryOverlayDictionary((CFMutableDictionaryRef) CFDictionaryGetValue(changesRecord, kCFNull), newKeysAndValues);
420
421 CFDictionaryForEach(changesRecord, ^(const void *key, const void *value) {
422 if (isArray(value) && (sender == NULL || sender != key)) {
423 CFArrayAppendKeys((CFMutableArrayRef) value, newKeysAndValues);
424 if (CFDictionaryGetCount(newKeysAndValues))
425 changes_added = true;
426 }
427 });
428
429 if (changes_added)
430 secnotice("changes", "Changes from %@: %@", sender, newKeysAndValues);
431
432 CFDictionaryRemoveAllValues(newKeysAndValues);
433
434 return changes_added;
435 }
436
437 static bool FillAllChanges(CFMutableDictionaryRef changes) {
438 __block bool changed = false;
439
440 CFMutableSetRef changedAccounts = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL);
441
442 CFArrayForEach(key_transports, ^(const void *value) {
443 CKKeyParameterTest* tpt = (__bridge CKKeyParameterTest*) value;
444 if (AddNewChanges(changes, SOSTransportKeyParameterTestGetChanges(tpt), SOSTransportKeyParameterTestGetAccount(tpt))) {
445 changed |= true;
446 CFSetAddValue(changedAccounts, (__bridge CFTypeRef)(SOSTransportKeyParameterTestGetAccount(tpt)));
447 }
448 SOSTransportKeyParameterTestClearChanges(tpt);
449 });
450 CFArrayForEach(circle_transports, ^(const void *value) {
451 SOSCircleStorageTransportTest *tpt = (__bridge SOSCircleStorageTransportTest *) value;
452 if (AddNewChanges(changes, [tpt SOSTransportCircleTestGetChanges], [tpt getAccount])) {
453 changed |= true;
454 CFSetAddValue(changedAccounts, (__bridge CFTypeRef)SOSTransportCircleTestGetAccount(tpt));
455 }
456 SOSTransportCircleTestClearChanges(tpt);
457 });
458 CFArrayForEach(message_transports, ^(const void *value) {
459 if([(__bridge SOSMessage*)value SOSTransportMessageGetTransportType] == kKVSTest){
460 SOSMessageKVSTest* tpt = (__bridge SOSMessageKVSTest*) value;
461 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt), kCFNull);
462 if (AddNewChanges(changes, SOSTransportMessageKVSTestGetChanges(tpt), SOSTransportMessageKVSTestGetAccount(tpt))) {
463 changed |= true;
464 CFSetAddValue(changedAccounts, (__bridge CFTypeRef)(SOSTransportMessageKVSTestGetAccount(tpt)));
465 }
466 SOSTransportMessageTestClearChanges(tpt);
467 }
468 });
469
470 secnotice("process-changes", "Accounts with change (%@): %@", changed ? CFSTR("YES") : CFSTR("NO"), changedAccounts);
471
472 CFReleaseNull(changedAccounts);
473
474 return changed;
475 }
476
477 static void FillChanges(CFMutableDictionaryRef changes, SOSAccount* forAccount)
478 {
479 CFArrayForEach(key_transports, ^(const void *value) {
480 CKKeyParameterTest* tpt = (__bridge CKKeyParameterTest*) value;
481 if(CFEqualSafe((__bridge CFTypeRef)(forAccount), (__bridge CFTypeRef)(SOSTransportKeyParameterTestGetAccount(tpt)))){
482 AddNewChanges(changes, SOSTransportKeyParameterTestGetChanges(tpt), SOSTransportKeyParameterTestGetAccount(tpt));
483 SOSTransportKeyParameterTestClearChanges(tpt);
484 }
485 });
486 CFArrayForEach(circle_transports, ^(const void *value) {
487 SOSCircleStorageTransportTest* tpt = (__bridge SOSCircleStorageTransportTest*) value;
488 if([forAccount isEqual: SOSTransportCircleTestGetAccount(tpt)]){
489 AddNewChanges(changes, [tpt SOSTransportCircleTestGetChanges], SOSTransportCircleTestGetAccount(tpt));
490 SOSTransportCircleTestClearChanges(tpt);
491 }
492 });
493 CFArrayForEach(message_transports, ^(const void *value) {
494 if([(__bridge SOSMessage*)value SOSTransportMessageGetTransportType] == kKVSTest){
495 SOSMessageKVSTest* tpt = (__bridge SOSMessageKVSTest*) value;
496 if(CFEqualSafe((__bridge CFTypeRef)(forAccount), (__bridge CFTypeRef)(SOSTransportMessageKVSTestGetAccount(tpt)))){
497 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt), kCFNull);
498 AddNewChanges(changes, SOSTransportMessageKVSTestGetChanges(tpt), SOSTransportMessageKVSTestGetAccount(tpt));
499 SOSTransportMessageTestClearChanges(tpt);
500 }
501 }
502 });
503
504 }
505
506 static inline void FillChangesMulti(CFMutableDictionaryRef changes, SOSAccount* account, ...)
507 {
508 SOSAccount* next_account = account;
509 va_list argp;
510 va_start(argp, account);
511 while(next_account != NULL) {
512 FillChanges(changes, next_account);
513 next_account = va_arg(argp, SOSAccount*);
514 }
515 }
516
517 static inline CFMutableArrayRef CFDictionaryCopyKeys(CFDictionaryRef dictionary)
518 {
519 CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
520
521 CFArrayAppendKeys(result, dictionary);
522
523 return result;
524 }
525
526 #define kFeedChangesToTestCount 1
527 static inline void FeedChangesTo(CFMutableDictionaryRef changes, SOSAccount* acct)
528 {
529 CFDictionaryRef full_list = (CFDictionaryRef) CFDictionaryGetValue(changes, kCFNull);
530
531 if (!isDictionary(full_list))
532 return; // Nothing recorded to send!
533
534 CFMutableArrayRef account_pending_keys = (CFMutableArrayRef)CFDictionaryGetValue(changes, (__bridge CFTypeRef)(acct));
535
536 if (!isArray(account_pending_keys)) {
537 account_pending_keys = CFDictionaryCopyKeys(full_list);
538 CFDictionaryAddValue(changes, (__bridge CFTypeRef)(acct), account_pending_keys);
539 CFReleaseSafe(account_pending_keys); // The dictionary keeps it, we don't retain it here.
540 }
541
542 CFMutableDictionaryRef account_pending_messages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
543 CFArrayForEach(account_pending_keys, ^(const void *value) {
544 CFDictionaryAddValue(account_pending_messages, value, CFDictionaryGetValue(full_list, value));
545 });
546
547 secnotice("changes", "Changes for %@:", SOSTransportKeyParameterTestGetName((CKKeyParameterTest*) acct.key_transport));
548
549 CFDictionaryForEach(account_pending_messages, ^(const void *key, const void *value) {
550 secnotice("changes", " %@", key);
551 });
552
553 if(CFDictionaryGetCount(account_pending_messages) == 0) {
554 CFReleaseNull(account_pending_messages);
555 return;
556 }
557
558 __block CFMutableArrayRef handled = NULL;
559 [acct performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
560 __block CFErrorRef error = NULL;
561 ok(handled = SOSTransportDispatchMessages(txn, account_pending_messages, &error), "SOSTransportHandleMessages failed (%@)", error);
562 CFReleaseNull(error);
563 }];
564
565 if (isArray(handled)) {
566 CFArrayForEach(handled, ^(const void *value) {
567 CFArrayRemoveAllValue(account_pending_keys, value);
568 });
569 }
570 CFReleaseNull(account_pending_messages);
571 CFReleaseNull(handled);
572 }
573
574 #define kFeedChangesToMultieTestCountPer 1
575
576 static inline void FeedChangesToMultiV(CFMutableDictionaryRef changes, va_list argp)
577 {
578 SOSAccount* account = NULL;
579 while((account = va_arg(argp, SOSAccount*)) != NULL) {
580 FeedChangesTo(changes, account);
581 }
582 }
583
584 static inline void FeedChangesToMulti(CFMutableDictionaryRef changes, ...)
585 {
586 va_list argp;
587 va_start(argp, changes);
588
589 FeedChangesToMultiV(changes, argp);
590
591 va_end(argp);
592 }
593
594 static inline void InjectChangeToMulti(CFMutableDictionaryRef changes,
595 CFStringRef changeKey, CFTypeRef changeValue, ...)
596 {
597 CFMutableDictionaryRef changes_to_send = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
598 changeKey, changeValue,
599 NULL);
600 AddNewChanges(changes, changes_to_send, NULL);
601 CFReleaseNull(changes_to_send);
602
603 va_list argp;
604 va_start(argp, changeValue);
605 FeedChangesToMultiV(changes, argp);
606 va_end(argp);
607 }
608
609 static inline bool ProcessChangesOnceV(CFMutableDictionaryRef changes, va_list argp)
610 {
611 bool result = FillAllChanges(changes);
612
613 FeedChangesToMultiV(changes, argp);
614
615 return result;
616 }
617
618
619 static inline bool ProcessChangesOnce(CFMutableDictionaryRef changes, ...)
620 {
621 va_list argp;
622 va_start(argp, changes);
623
624 bool result = ProcessChangesOnceV(changes, argp);
625
626 va_end(argp);
627
628 return result;
629 }
630
631 static inline int ProcessChangesUntilNoChange(CFMutableDictionaryRef changes, ...)
632 {
633 va_list argp;
634 va_start(argp, changes);
635
636 int result = 0;
637 bool new_data = false;
638 do {
639 va_list argp_copy;
640 va_copy(argp_copy, argp);
641
642 new_data = ProcessChangesOnceV(changes, argp_copy);
643
644 ++result;
645
646 va_end(argp_copy);
647 } while (new_data);
648
649 va_end(argp);
650
651 return result;
652
653 }
654
655 //
656 // MARK: Account creation
657 //
658
659 static CFStringRef modelFromType(SOSPeerInfoDeviceClass cls) {
660 switch(cls) {
661 case SOSPeerInfo_macOS: return CFSTR("Mac Pro");
662 case SOSPeerInfo_iOS: return CFSTR("iPhone");
663 case SOSPeerInfo_iCloud: return CFSTR("iCloud");
664 case SOSPeerInfo_watchOS: return CFSTR("needWatchOSDeviceName");
665 case SOSPeerInfo_tvOS: return CFSTR("needTVOSDeviceName");
666 default: return CFSTR("GENERICOSTHING");
667 }
668 }
669
670 static inline SOSAccount* CreateAccountForLocalChangesWithStartingAttributes(CFStringRef name, CFStringRef data_source_name, SOSPeerInfoDeviceClass devclass, CFStringRef serial, CFBooleanRef preferIDS, CFBooleanRef preferIDSFragmentation, CFBooleanRef preferIDSACKModel, CFStringRef transportType, CFStringRef deviceID) {
671
672 SOSDataSourceFactoryRef factory = SOSTestDataSourceFactoryCreate();
673 SOSDataSourceRef ds = SOSTestDataSourceCreate();
674 SOSTestDataSourceFactorySetDataSource(factory, data_source_name, ds);
675 SOSEngineRef engine = SOSEngineCreate(ds, NULL);
676 ds->engine = engine;
677
678 CFMutableDictionaryRef gestalt = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
679 CFDictionaryAddValue(gestalt, kPIUserDefinedDeviceNameKey, name);
680 CFDictionaryAddValue(gestalt, kPIDeviceModelNameKey, modelFromType(devclass));
681 CFDictionaryAddValue(gestalt, kPIOSVersionKey, CFSTR("TESTRUN"));
682
683 CFMutableDictionaryRef testV2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
684 CFDictionaryAddValue(testV2dict, sSerialNumberKey, serial);
685 SOSAccount* result = SOSAccountCreateTest(kCFAllocatorDefault, name, gestalt, factory);
686 [result.trust updateV2Dictionary:result v2:testV2dict];
687
688 CFReleaseSafe(SOSAccountCopyUUID(result));
689
690 CFReleaseNull(gestalt);
691 CFReleaseNull(testV2dict);
692
693 return result;
694 }
695
696 static CFStringRef sGestaltTest = CFSTR("GestaltTest");
697 static CFStringRef sV2Test = CFSTR("V2Test");
698 static inline CFDictionaryRef SOSTestSaveStaticAccountState(SOSAccount* account) {
699 CFMutableDictionaryRef retval = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
700 CFDictionaryRef gestalt = SOSAccountCopyGestalt(account);
701 CFDictionaryRef v2dictionary = SOSAccountCopyV2Dictionary(account);
702 CFDictionaryAddValue(retval, sGestaltTest, gestalt);
703 CFDictionaryAddValue(retval, sV2Test, v2dictionary);
704 CFReleaseNull(gestalt);
705 CFReleaseNull(v2dictionary);
706 return retval;
707 }
708
709 static inline void SOSTestRestoreAccountState(SOSAccount* account, CFDictionaryRef saved) {
710 [account.trust updateGestalt:account newGestalt:CFDictionaryGetValue(saved, sGestaltTest)];
711 [account.trust updateV2Dictionary:account v2:CFDictionaryGetValue(saved, sV2Test)];
712 }
713
714 static CFStringRef CFStringCreateRandomHexWithLength(size_t len) {
715 if(len%2) len++;
716 CFDataRef data = CFDataCreateWithRandomBytes(len/2);
717 CFMutableStringRef retval = CFStringCreateMutable(kCFAllocatorDefault, len);
718 CFStringAppendHexData(retval, data);
719 CFReleaseNull(data);
720 return retval;
721 }
722
723 static inline SOSAccount* CreateAccountForLocalChanges(CFStringRef name, CFStringRef data_source_name)
724 {
725 CFStringRef randomSerial = CFStringCreateRandomHexWithLength(8);
726 CFStringRef randomDevID = CFStringCreateRandomHexWithLength(16);
727 SOSAccount* retval = CreateAccountForLocalChangesWithStartingAttributes(name, data_source_name, SOSPeerInfo_iOS, randomSerial,
728 kCFBooleanTrue, kCFBooleanTrue, kCFBooleanTrue, SOSTransportMessageTypeKVS, randomDevID);
729
730 CFReleaseNull(randomSerial);
731 CFReleaseNull(randomDevID);
732 return retval;
733 }
734
735 static inline SOSAccount* CreateAccountForLocalChangesFromData(CFDataRef flattenedData, CFStringRef name, CFStringRef data_source_name)
736 {
737 SOSDataSourceFactoryRef factory = SOSTestDataSourceFactoryCreate();
738 SOSDataSourceRef ds = SOSTestDataSourceCreate();
739 SOSTestDataSourceFactorySetDataSource(factory, data_source_name, ds);
740 SOSEngineRef engine = SOSEngineCreate(ds, NULL);
741 ds->engine = engine;
742
743 SOSAccount* result = SOSAccountCreateTestFromData(kCFAllocatorDefault, flattenedData, name, factory);
744
745 return result;
746 }
747
748
749
750 static inline int countPeers(SOSAccount* account) {
751 CFErrorRef error = NULL;
752 CFArrayRef peers;
753
754 peers = SOSAccountCopyPeers(account, &error);
755 if(!peers)
756 return 0;
757 int retval = (int) CFArrayGetCount(peers);
758 CFReleaseNull(error);
759 CFReleaseNull(peers);
760 return retval;
761 }
762
763 static inline int countActivePeers(SOSAccount* account) {
764 CFErrorRef error = NULL;
765 CFArrayRef peers;
766
767 peers = SOSAccountCopyActivePeers(account, &error);
768 if(!peers)
769 return 0;
770 int retval = (int) CFArrayGetCount(peers);
771 CFReleaseNull(error);
772 CFReleaseNull(peers);
773 return retval;
774 }
775
776 static inline int countActiveValidPeers(SOSAccount* account) {
777 CFErrorRef error = NULL;
778 CFArrayRef peers;
779
780 peers = SOSAccountCopyActiveValidPeers(account, &error);
781 if(!peers)
782 return 0;
783 int retval = (int) CFArrayGetCount(peers);
784 CFReleaseNull(error);
785 CFReleaseNull(peers);
786 return retval;
787 }
788
789 static inline int countApplicants(SOSAccount* account) {
790 CFErrorRef error = NULL;
791 CFArrayRef applicants = SOSAccountCopyApplicants(account, &error);
792 int retval = 0;
793
794 if(applicants) retval = (int)CFArrayGetCount(applicants);
795 CFReleaseNull(error);
796 CFReleaseNull(applicants);
797 return retval;
798 }
799
800
801 static inline void showActiveValidPeers(SOSAccount* account) {
802 CFErrorRef error = NULL;
803 CFArrayRef peers;
804
805 peers = SOSAccountCopyActiveValidPeers(account, &error);
806 CFArrayForEach(peers, ^(const void *value) {
807 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
808 ok(0, "Active Valid Peer %@", pi);
809 });
810 CFReleaseNull(peers);
811 }
812
813 #define ok_or_quit(COND,MESSAGE,LABEL) ok(COND, MESSAGE); if(!(COND)) goto LABEL
814
815 static inline bool testAccountPersistence(SOSAccount* account) {
816
817 __block bool retval = false;
818 __block NSData* accountDER = NULL;
819
820 SOSDataSourceFactoryRef test_factory = SOSTestDataSourceFactoryCreate();
821 SOSDataSourceRef test_source = SOSTestDataSourceCreate();
822 SOSTestDataSourceFactorySetDataSource(test_factory, CFSTR("TestType"), test_source);
823
824 SOSAccountCheckHasBeenInSync_wTxn(account);
825
826 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
827 NSError* error = nil;
828
829 // DER encode account to accountData - this allows checking discreet DER functions
830 size_t size = [account.trust getDEREncodedSize:account err:&error];
831 error = nil;
832 uint8_t buffer[size];
833 uint8_t* start = [account.trust encodeToDER:account err:&error start:buffer end:buffer + sizeof(buffer)];
834 error = nil;
835
836 ok_or_quit(start, "successful encoding", errOut);
837 ok_or_quit(start == buffer, "Used whole buffer", errOut);
838
839 accountDER = [NSData dataWithBytes:buffer length:size];
840 ok_or_quit(accountDER, "Made CFData for Account", errOut);
841
842 retval = true;
843 errOut:
844 do {} while(0);
845 }];
846
847 SOSAccount* reinflatedAccount = NULL;
848 NSError* error = nil;
849
850 if(!retval) {
851 error = nil;
852 return retval;
853 }
854
855 // Re-inflate to "inflated"
856 reinflatedAccount = [SOSAccount accountFromData:accountDER
857 factory:test_factory
858 error:&error];
859 ok(reinflatedAccount, "inflated: %@", error);
860 error = nil;
861
862 ok(CFEqualSafe((__bridge CFTypeRef)reinflatedAccount, (__bridge CFTypeRef)account), "Compares");
863
864 // Repeat through SOSAccountCopyEncodedData() interface - this is the normally called combined interface
865 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
866 NSError* error = nil;
867 accountDER = [account encodedData:&error];
868 }];
869
870 error = nil;
871 SOSAccount* reinflatedAccount2 = NULL;
872
873 reinflatedAccount2 = [SOSAccount accountFromData:accountDER factory:test_factory error:&error];
874 ok(reinflatedAccount2, "inflated2: %@", error);
875 ok(CFEqual((__bridge CFTypeRef)account, (__bridge CFTypeRef)reinflatedAccount2), "Compares");
876
877 retval = true;
878 error = nil;
879 return retval;
880 }
881
882 static inline bool SOSTestStartCircleWithAccount(SOSAccount* account, CFMutableDictionaryRef changes, CFStringRef cfaccount, CFDataRef cfpassword) {
883 bool retval = false;
884 if(!SOSAccountAssertUserCredentialsAndUpdate(account, cfaccount, cfpassword, NULL))
885 return retval;
886 is(ProcessChangesUntilNoChange(changes, account, NULL), 1, "updates");
887 if(!SOSAccountResetToOffering_wTxn(account, NULL))
888 return retval;
889 is(ProcessChangesUntilNoChange(changes, account, NULL), 1, "updates");
890 retval = true;
891
892 return retval;
893 }
894
895 static inline bool SOSTestApproveRequest(SOSAccount* approver, CFIndex napplicants) {
896 bool retval = false;
897 CFErrorRef error = NULL;
898 CFArrayRef applicants = SOSAccountCopyApplicants(approver, &error);
899
900 ok(applicants && CFArrayGetCount(applicants) == napplicants, "See %ld applicant(s) %@ (%@)", napplicants, applicants, error);
901 CFStringRef approvername = SOSAccountCopyName(approver);
902 ok((retval = SOSAccountAcceptApplicants(approver, applicants, &error)), "%@ accepts (%@)", approvername, error);
903 CFReleaseNull(error);
904 CFReleaseNull(applicants);
905 CFReleaseNull(approvername);
906
907 return retval;
908 }
909
910 #define DROP_USERKEY true
911 #define KEEP_USERKEY false
912
913 static inline bool SOSTestJoinWith(CFDataRef cfpassword, CFStringRef cfaccount, CFMutableDictionaryRef changes, SOSAccount* joiner) {
914 CFErrorRef error = NULL;
915 // retval will return op failures, not count failures - we'll still report those from in here.
916 bool retval = false;
917
918 FeedChangesTo(changes, joiner);
919
920 ok(SOSAccountAssertUserCredentialsAndUpdate(joiner, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
921 CFReleaseNull(error);
922
923 ProcessChangesUntilNoChange(changes, joiner, NULL);
924
925 ok(retval = SOSAccountJoinCircles_wTxn(joiner, &error), "Applying (%@)", error);
926 CFReleaseNull(error);
927 return retval;
928 }
929
930 static inline bool SOSTestJoinWithApproval(CFDataRef cfpassword, CFStringRef cfaccount, CFMutableDictionaryRef changes, SOSAccount* approver, SOSAccount* joiner, bool dropUserKey, int expectedCount, bool expectCleanup) {
931 //CFErrorRef error = NULL;
932 // retval will return op failures, not count failures - we'll still report those from in here.
933 bool retval = false;
934
935 ok(retval = SOSTestJoinWith(cfpassword, cfaccount, changes, joiner), "Application Made");
936
937 ProcessChangesUntilNoChange(changes, approver, joiner, NULL);
938
939 int nrounds = 2;
940 if(dropUserKey) SOSAccountPurgePrivateCredential(joiner); // lose the userKey so we don't "fix" the ghost problem yet.
941 else nrounds = 3;
942
943 if(expectCleanup) nrounds++;
944
945 ok(retval &= SOSTestApproveRequest(approver, 1), "Accepting Request to Join");
946 ProcessChangesUntilNoChange(changes, approver, joiner, NULL);
947
948 accounts_agree_internal("Successful join shows same circle view", joiner, approver, false);
949 is(countPeers(joiner), expectedCount, "There should be %d valid peers", expectedCount);
950 return retval;
951 }
952
953
954 static inline bool SOSTestChangeAccountDeviceName(SOSAccount* account, CFStringRef name) {
955 bool retval = false;
956 CFMutableDictionaryRef mygestalt = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, SOSPeerGetGestalt(account.peerInfo));
957 require_quiet(mygestalt, retOut);
958 CFDictionarySetValue(mygestalt, kPIUserDefinedDeviceNameKey, name);
959 retval = [account.trust updateGestalt:account newGestalt:mygestalt];
960 retOut:
961 CFReleaseNull(mygestalt);
962 return retval;
963 }
964
965 /*
966 * this simulates a piggy-back join at the account level
967 */
968
969 static inline bool SOSTestJoinThroughPiggyBack(CFDataRef cfpassword, CFStringRef cfaccount, CFMutableDictionaryRef changes, SOSAccount* approver, SOSAccount* joiner, bool dropUserKey, int expectedCount, bool expectCleanup) {
970 // retval will return op failures, not count failures - we'll still report those from in here.
971 bool retval = false;
972 CFErrorRef error = NULL;
973
974 ok(SOSAccountAssertUserCredentialsAndUpdate(approver, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
975 CFReleaseNull(error);
976 // This makes sure the joiner sees the current key parameters
977 ProcessChangesUntilNoChange(changes, approver, joiner, NULL);
978
979 SecKeyRef privKey = SOSAccountGetPrivateCredential(approver, &error);
980 ok(privKey, "got privkey from approver (%@)", error);
981 CFReleaseNull(error);
982
983 ok(SOSAccountTryUserPrivateKey(joiner, privKey, &error), "assert same credentials on joiner (%@)", error);
984 CFReleaseNull(error);
985 // This gives the joiner a chance to see the current circle - this is the account-level equivalent of the Flush added to stashAccountCredential
986 ProcessChangesUntilNoChange(changes, approver, joiner, NULL);
987
988 SOSPeerInfoRef joinerPI = SOSAccountCopyApplication(joiner, &error);
989 ok(joinerPI, "Joiner peerinfo available as application %@", error);
990 CFReleaseNull(error);
991
992 CFDataRef theBlob = SOSAccountCopyCircleJoiningBlob(approver, joinerPI, &error);
993 ok(theBlob, "Made a joining blob (%@)", error);
994 CFReleaseNull(error);
995
996
997 bool joined = SOSAccountJoinWithCircleJoiningBlob(joiner, theBlob, kPiggyV1, &error);
998 ok(joined, "Joiner posted circle with itself in it (%@)", error);
999 CFReleaseNull(error);
1000
1001 CFReleaseNull(joinerPI);
1002 CFReleaseNull(theBlob);
1003
1004 is(ProcessChangesUntilNoChange(changes, approver, joiner, NULL), 2, "updates");
1005
1006 ok((retval = [joiner isInCircle:NULL]), "Joiner is in");
1007
1008 accounts_agree_internal("Successful join shows same circle view", joiner, approver, false);
1009 is(countPeers(joiner), expectedCount, "There should be %d valid peers", expectedCount);
1010 return retval;
1011 }
1012
1013
1014 static inline SOSAccount* SOSTestCreateAccountAsSerialClone(CFStringRef name, SOSPeerInfoDeviceClass devClass, CFStringRef serial, CFStringRef idsID) {
1015 return CreateAccountForLocalChangesWithStartingAttributes(name, CFSTR("TestSource"), devClass, serial, kCFBooleanTrue, kCFBooleanTrue, kCFBooleanTrue, SOSTransportMessageTypeKVS, idsID);
1016 }
1017
1018 static inline bool SOSTestMakeGhostInCircle(CFStringRef name, SOSPeerInfoDeviceClass devClass, CFStringRef serial, CFStringRef idsID,
1019 CFDataRef cfpassword, CFStringRef cfaccount, CFMutableDictionaryRef changes,
1020 SOSAccount* approver, int expectedCount) {
1021 bool retval = false;
1022 SOSAccount* ghostAccount = SOSTestCreateAccountAsSerialClone(name, devClass, serial, idsID);
1023 ok(ghostAccount, "Created Ghost Account");
1024 require_quiet(ghostAccount, retOut);
1025 if(!ghostAccount) return false;
1026 ok(retval = SOSTestJoinWithApproval(cfpassword, cfaccount, changes, approver, ghostAccount, DROP_USERKEY, expectedCount, true), "Ghost Joined Circle with expected result");
1027 retOut:
1028 return retval;
1029 }
1030
1031 static inline void SOSTestCleanup() {
1032 SOSUnregisterAllTransportMessages();
1033 SOSUnregisterAllTransportCircles();
1034 SOSUnregisterAllTransportKeyParameters();
1035 CFArrayRemoveAllValues(key_transports);
1036 CFArrayRemoveAllValues(circle_transports);
1037 CFArrayRemoveAllValues(message_transports);
1038 }
1039
1040
1041 #endif