5 // Created by Richard Murphy on 2/23/15.
10 #include "SOSRingTypes.h"
11 #include "SOSRingBasic.h"
12 #include "SOSRingBackup.h"
13 #include <Security/SecureObjectSync/SOSAccountPriv.h>
14 #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
16 static ringFuncs ringTypes
[] = {
20 static const size_t typecount
= sizeof(ringTypes
) / sizeof(ringFuncs
);
22 static bool SOSRingValidType(SOSRingType type
) {
23 return type
< typecount
;
26 // MARK: Exported Functions
29 SOSRingRef
SOSRingCreate(CFStringRef name
, CFStringRef myPeerID
, SOSRingType type
, CFErrorRef
*error
) {
30 require(SOSRingValidType(type
), errOut
);
31 require(ringTypes
[type
]->sosRingCreate
, errOut
);
32 return ringTypes
[type
]->sosRingCreate(name
, myPeerID
, error
);
34 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
38 bool SOSRingResetToEmpty(SOSRingRef ring
, CFStringRef myPeerID
, CFErrorRef
*error
) {
39 SOSRingAssertStable(ring
);
40 SOSRingType type
= SOSRingGetType(ring
);
41 require(SOSRingValidType(type
), errOut
);
42 require(ringTypes
[type
]->sosRingResetToEmpty
, errOut
);
43 return ringTypes
[type
]->sosRingResetToEmpty(ring
, myPeerID
, error
);
45 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
49 bool SOSRingResetToOffering(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
50 SOSRingAssertStable(ring
);
51 SOSRingType type
= SOSRingGetType(ring
);
52 require(SOSRingValidType(type
), errOut
);
53 require(ringTypes
[type
]->sosRingResetToOffering
, errOut
);
54 return ringTypes
[type
]->sosRingResetToOffering(ring
, user_privkey
, requestor
, error
);
56 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
60 SOSRingStatus
SOSRingDeviceIsInRing(SOSRingRef ring
, CFStringRef peerID
) {
61 SOSRingAssertStable(ring
);
62 SOSRingType type
= SOSRingGetType(ring
);
63 require(SOSRingValidType(type
), errOut
);
64 require(ringTypes
[type
]->sosRingDeviceIsInRing
, errOut
);
65 return ringTypes
[type
]->sosRingDeviceIsInRing(ring
, peerID
);
70 bool SOSRingApply(SOSRingRef ring
, SecKeyRef user_pubkey
, SOSFullPeerInfoRef fpi
, CFErrorRef
*error
) {
71 SOSRingAssertStable(ring
);
72 SOSRingType type
= SOSRingGetType(ring
);
73 require(SOSRingValidType(type
), errOut
);
74 require(ringTypes
[type
]->sosRingApply
, shortCircuit
);
75 require_quiet(SOSPeerInfoApplicationVerify(SOSFullPeerInfoGetPeerInfo(fpi
), user_pubkey
, error
), errOut2
);
77 return ringTypes
[type
]->sosRingApply(ring
, user_pubkey
, fpi
, error
);
79 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
82 SOSCreateError(kSOSErrorBadSignature
, CFSTR("FullPeerInfo fails userkey signature check"), NULL
, error
);
88 bool SOSRingWithdraw(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
89 SOSRingAssertStable(ring
);
90 SOSRingType type
= SOSRingGetType(ring
);
91 require(SOSRingValidType(type
), errOut
);
92 require(ringTypes
[type
]->sosRingWithdraw
, shortCircuit
);
93 return ringTypes
[type
]->sosRingWithdraw(ring
, user_privkey
, requestor
, error
);
95 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
101 bool SOSRingGenerationSign(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
102 SOSRingAssertStable(ring
);
103 SOSRingType type
= SOSRingGetType(ring
);
104 require(SOSRingValidType(type
), errOut
);
105 require(ringTypes
[type
]->sosRingGenerationSign
, shortCircuit
);
106 return ringTypes
[type
]->sosRingGenerationSign(ring
, user_privkey
, requestor
, error
);
108 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
114 bool SOSRingConcordanceSign(SOSRingRef ring
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
115 SOSRingAssertStable(ring
);
116 SOSRingType type
= SOSRingGetType(ring
);
117 require(SOSRingValidType(type
), errOut
);
118 require(ringTypes
[type
]->sosRingConcordanceSign
, shortCircuit
);
119 return ringTypes
[type
]->sosRingConcordanceSign(ring
, requestor
, error
);
121 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
127 SOSConcordanceStatus
SOSRingConcordanceTrust(SOSFullPeerInfoRef me
, CFSetRef peers
,
128 SOSRingRef knownRing
, SOSRingRef proposedRing
,
129 SecKeyRef knownPubkey
, SecKeyRef userPubkey
,
130 CFStringRef excludePeerID
, CFErrorRef
*error
) {
131 SOSRingAssertStable(knownRing
);
132 SOSRingAssertStable(proposedRing
);
133 SOSRingType type1
= SOSRingGetType(knownRing
);
134 SOSRingType type2
= SOSRingGetType(proposedRing
);
135 require(SOSRingValidType(type1
), errOut
);
136 require(SOSRingValidType(type2
), errOut
);
137 require(type1
== type2
, errOut
);
139 secnotice("ring", "concordance trust (%s) knownRing: %@ proposedRing: %@ knownkey: %@ userkey: %@ excluded: %@",
140 ringTypes
[type1
]->typeName
, knownRing
, proposedRing
, knownPubkey
, userPubkey
, excludePeerID
);
142 require(ringTypes
[type1
]->sosRingConcordanceTrust
, errOut
);
143 return ringTypes
[type1
]->sosRingConcordanceTrust(me
, peers
, knownRing
, proposedRing
, knownPubkey
, userPubkey
, excludePeerID
, error
);
145 return kSOSConcordanceError
;
148 bool SOSRingAccept(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
149 SOSRingAssertStable(ring
);
150 SOSRingType type
= SOSRingGetType(ring
);
151 require(SOSRingValidType(type
), errOut
);
152 require(ringTypes
[type
]->sosRingAccept
, shortCircuit
);
153 return ringTypes
[type
]->sosRingAccept(ring
, user_privkey
, requestor
, error
);
155 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
161 bool SOSRingReject(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
162 SOSRingAssertStable(ring
);
163 SOSRingType type
= SOSRingGetType(ring
);
164 require(SOSRingValidType(type
), errOut
);
165 require(ringTypes
[type
]->sosRingReject
, shortCircuit
);
166 return ringTypes
[type
]->sosRingReject(ring
, user_privkey
, requestor
, error
);
168 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
174 bool SOSRingSetPayload(SOSRingRef ring
, SecKeyRef user_privkey
, CFDataRef payload
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
175 SOSRingAssertStable(ring
);
176 SOSRingType type
= SOSRingGetType(ring
);
177 require(SOSRingValidType(type
), errOut
);
178 require(ringTypes
[type
]->sosRingSetPayload
, errOut
);
179 return ringTypes
[type
]->sosRingSetPayload(ring
, user_privkey
, payload
, requestor
, error
);
181 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
185 CFDataRef
SOSRingGetPayload(SOSRingRef ring
, CFErrorRef
*error
) {
186 SOSRingAssertStable(ring
);
187 SOSRingType type
= SOSRingGetType(ring
);
188 require(SOSRingValidType(type
), errOut
);
189 require(ringTypes
[type
]->sosRingGetPayload
, errOut
);
190 return ringTypes
[type
]->sosRingGetPayload(ring
, error
);
192 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
196 CFSetRef
SOSRingGetBackupViewset(SOSRingRef ring
, CFErrorRef
*error
) {
197 SOSRingAssertStable(ring
);
198 SOSRingType type
= SOSRingGetType(ring
);
199 require(kSOSRingBackup
== type
, errOut
);
200 return SOSRingGetBackupViewset_Internal(ring
);
202 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not backup ring type"), NULL
, error
);
206 static bool isBackupRing(SOSRingRef ring
, CFErrorRef
*error
) {
207 SOSRingType type
= SOSRingGetType(ring
);
208 require_quiet(kSOSRingBackup
== type
, errOut
);
211 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not backup ring type"), NULL
, error
);
215 bool SOSRingSetBackupKeyBag(SOSRingRef ring
, SOSFullPeerInfoRef fpi
, CFSetRef viewSet
, SOSBackupSliceKeyBagRef bskb
, CFErrorRef
*error
) {
216 SOSRingAssertStable(ring
);
217 CFDataRef bskb_as_data
= NULL
;
219 require_quiet(isBackupRing(ring
, error
), errOut
);
221 bskb_as_data
= SOSBSKBCopyEncoded(bskb
, error
);
222 result
= bskb_as_data
&&
223 SOSRingSetBackupViewset_Internal(ring
, viewSet
) &&
224 SOSRingSetPayload(ring
, NULL
, bskb_as_data
, fpi
, error
);
226 CFReleaseNull(bskb_as_data
);
230 SOSBackupSliceKeyBagRef
SOSRingCopyBackupSliceKeyBag(SOSRingRef ring
, CFErrorRef
*error
) {
231 SOSRingAssertStable(ring
);
233 CFDataRef bskb_as_data
= NULL
;
234 SOSBackupSliceKeyBagRef result
= NULL
;
235 require_quiet(isBackupRing(ring
, error
), errOut
);
237 bskb_as_data
= SOSRingGetPayload(ring
, error
);
238 require_quiet(bskb_as_data
, errOut
);
240 result
= SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault
, bskb_as_data
, error
);
247 bool SOSRingPKTrusted(SOSRingRef ring
, SecKeyRef pubkey
, CFErrorRef
*error
) {
248 SOSRingAssertStable(ring
);
249 SOSRingType type
= SOSRingGetType(ring
);
250 require(SOSRingValidType(type
), errOut
);
251 return SOSRingVerify(ring
, pubkey
, error
);
253 SOSCreateError(kSOSErrorUnexpectedType
, CFSTR("Not valid ring type"), NULL
, error
);
257 bool SOSRingPeerTrusted(SOSRingRef ring
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
258 SOSPeerInfoRef pi
= SOSFullPeerInfoGetPeerInfo(requestor
);
259 SecKeyRef pubkey
= SOSPeerInfoCopyPubKey(pi
);
260 bool retval
= SOSRingPKTrusted(ring
, pubkey
, error
);
261 CFReleaseNull(pubkey
);
268 bool SOSAccountKnowsRings(SOSAccountRef account
, CFErrorRef
*error
) {
269 if(account
->rings
) return true;
270 SOSCreateError(kSOSErrorUnsupported
, CFSTR("This account doesn't support rings"), NULL
, error
);
276 bool SOSRingRequirementKnown(SOSAccountRef account
, CFStringRef name
, CFErrorRef
*error
) {
278 require_quiet(SOSAccountKnowsRings(account
, error
), errOut
);
279 retval
= CFDictionaryContainsValue(account
->rings
, name
);
284 bool SOSRingRequirementCreate(SOSAccountRef account
, CFStringRef name
, SOSRingType type
, CFErrorRef
*error
) {
285 if(account
->rings
) return false;
286 if(CFDictionaryContainsValue(account
->rings
, name
)) return true;
287 if(!SOSRingValidType(type
)) return false;
288 SOSRingRef ring
= SOSRingCreate(NULL
, name
, type
, error
);
289 if(!ring
) return false;
290 CFDictionaryAddValue(account
->rings
, name
, ring
);
294 static SOSRingRef
getRingFromAccount(SOSAccountRef account
, CFStringRef name
, CFErrorRef
* error
) {
295 SOSRingRef retval
= NULL
;
296 require_quiet(SOSAccountKnowsRings(account
, error
), errOut
);
297 retval
= (SOSRingRef
) CFDictionaryGetValue(account
->rings
, name
);
305 bool SOSRingRequirementResetToOffering(SOSAccountRef account
, CFStringRef name
, CFErrorRef
* error
) {
306 SOSRingRef ring
= getRingFromAccount(account
, name
, error
);
307 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInfo(account
);
308 require_action_quiet(ring
, errOut
,
309 SOSCreateError(kSOSErrorNoCircle
, CFSTR("No ring by name specified"), NULL
, error
));
310 switch(SOSRingGetType(ring
)) {
313 SOSRingResetToOffering(ring
, account
->__user_private
, fpi
, error
);
320 bool SOSRingRequirementResetToEmpty(SOSAccountRef account
, CFStringRef name
, CFErrorRef
* error
) {
327 bool SOSRingRequirementRequestToJoin(SOSAccountRef account
, CFStringRef name
, CFErrorRef
* error
) {
331 bool SOSRingRequirementRemoveThisDevice(SOSAccountRef account
, CFStringRef name
, CFErrorRef
* error
) {
336 CFArrayRef
SOSRingRequirementGetApplicants(SOSAccountRef account
, CFStringRef name
, CFErrorRef
* error
) {
341 bool SOSRingRequirementAcceptApplicants(SOSAccountRef account
, CFStringRef name
, CFArrayRef applicants
, CFErrorRef
* error
) {
346 bool SOSRingRequirementRejectApplicants(SOSAccountRef account
, CFStringRef name
, CFArrayRef applicants
, CFErrorRef
*error
) {
351 bool SOSRingResetToOffering(SOSCircleRef circle
, SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
){
353 return SOSRingResetToEmpty(ring
, error
)
354 && SOSRingRequestAdmission(ring
, user_privkey
, requestor
, error
)
355 && SOSRingAcceptRequest(ring
, user_privkey
, requestor
, SOSFullPeerInfoGetPeerInfo(requestor
), error
);
359 static bool SOSRingRecordAdmissionRequest(SOSRingRef ring
, SecKeyRef user_pubkey
, CFStringRef peerID
, CFErrorRef
*error
) {
360 SOSRingAssertStable(ring
);
362 bool isPeer
= SOSRingHasPeerWithID(ring
, peerID
, error
);
363 require_action_quiet(!isPeer
, fail
, SOSCreateError(kSOSErrorAlreadyPeer
, CFSTR("Cannot request admission when already a peer"), NULL
, error
));
364 CFSetRemoveValue(ring
->rejections
, requestorPeerInfo
); // We remove from rejected list, in case?
365 CFSetSetValue(ring
->applicants
, requestorPeerInfo
);
374 bool SOSRingRequestReadmission(SOSRingRef ring
, SecKeyRef user_pubkey
, SOSPeerInfoRef peer
, CFErrorRef
*error
) {
375 bool success
= false;
377 require_quiet(SOSPeerInfoApplicationVerify(peer
, user_pubkey
, error
), fail
);
378 success
= SOSRingRecordAdmissionRequest(ring
, user_pubkey
, peer
, error
);
383 bool SOSRingRequestAdmission(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, CFErrorRef
*error
) {
384 bool success
= false;
386 SecKeyRef user_pubkey
= SecKeyCreatePublicFromPrivate(user_privkey
);
387 require_action_quiet(user_pubkey
, fail
, SOSCreateError(kSOSErrorBadKey
, CFSTR("No public key for key"), NULL
, error
));
389 require(SOSFullPeerInfoPromoteToApplication(requestor
, user_privkey
, error
), fail
);
391 success
= SOSRingRecordAdmissionRequest(ring
, user_pubkey
, SOSFullPeerInfoGetPeerInfo(requestor
), error
);
393 CFReleaseNull(user_pubkey
);
398 bool SOSRingRemovePeer(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef requestor
, SOSPeerInfoRef peer_to_remove
, CFErrorRef
*error
) {
399 SOSPeerInfoRef requestor_peer_info
= SOSFullPeerInfoGetPeerInfo(requestor
);
401 if (SOSRingHasApplicant(ring
, peer_to_remove
, error
)) {
402 return SOSRingRejectRequest(ring
, requestor
, peer_to_remove
, error
);
405 if (!SOSRingHasPeer(ring
, requestor_peer_info
, error
)) {
406 SOSCreateError(kSOSErrorAlreadyPeer
, CFSTR("Must be peer to remove peer"), NULL
, error
);
410 CFSetRemoveValue(ring
->peers
, peer_to_remove
);
412 SOSRingGenerationSign(ring
, user_privkey
, requestor
, error
);
417 bool SOSRingAcceptRequest(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef device_approver
, SOSPeerInfoRef peerInfo
, CFErrorRef
*error
) {
418 SOSRingAssertStable(ring
);
420 SecKeyRef publicKey
= NULL
;
423 require_action_quiet(CFSetContainsValue(ring
->applicants
, peerInfo
), fail
,
424 SOSCreateError(kSOSErrorNotApplicant
, CFSTR("Cannot accept non-applicant"), NULL
, error
));
426 publicKey
= SecKeyCreatePublicFromPrivate(user_privkey
);
427 require_quiet(SOSPeerInfoApplicationVerify(peerInfo
, publicKey
, error
), fail
);
429 CFSetRemoveValue(ring
->applicants
, peerInfo
);
430 CFSetSetValue(ring
->peers
, peerInfo
);
432 result
= SOSRingGenerationSign(ring
, user_privkey
, device_approver
, error
);
433 secnotice("ring", "Accepted %@", peerInfo
);
436 CFReleaseNull(publicKey
);
440 bool SOSRingWithdrawRequest(SOSRingRef ring
, SOSPeerInfoRef peerInfo
, CFErrorRef
*error
) {
441 SOSRingAssertStable(ring
);
443 CFSetRemoveValue(ring
->applicants
, peerInfo
);
448 bool SOSRingRemoveRejectedPeer(SOSRingRef ring
, SOSPeerInfoRef peerInfo
, CFErrorRef
*error
) {
449 SOSRingAssertStable(ring
);
451 CFSetRemoveValue(ring
->rejected_applicants
, peerInfo
);
457 bool SOSRingRejectRequest(SOSRingRef ring
, SOSFullPeerInfoRef device_rejector
,
458 SOSPeerInfoRef peerInfo
, CFErrorRef
*error
) {
459 SOSRingAssertStable(ring
);
461 if (CFEqual(SOSPeerInfoGetPeerID(peerInfo
), SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(device_rejector
))))
462 return SOSRingWithdrawRequest(ring
, peerInfo
, error
);
464 if (!CFSetContainsValue(ring
->applicants
, peerInfo
)) {
465 SOSCreateError(kSOSErrorNotApplicant
, CFSTR("Cannot reject non-applicant"), NULL
, error
);
469 CFSetRemoveValue(ring
->applicants
, peerInfo
);
470 CFSetSetValue(ring
->rejected_applicants
, peerInfo
);
472 // TODO: Maybe we sign the rejection with device_rejector.
477 bool SOSRingAcceptRequests(SOSRingRef ring
, SecKeyRef user_privkey
, SOSFullPeerInfoRef device_approver
,
479 // Returns true if we accepted someone and therefore have to post the ring back to KVS
480 __block
bool result
= false;
482 SOSRingForEachApplicant(ring
, ^(SOSPeerInfoRef peer
) {
483 if (!SOSRingAcceptRequest(ring
, user_privkey
, device_approver
, peer
, error
))
484 printf("error in SOSRingAcceptRequest\n");
486 secnotice("ring", "Accepted peer: %@", peer
);
492 SOSRingGenerationSign(ring
, user_privkey
, device_approver
, error
);
493 secnotice("ring", "Countersigned accepted requests");
499 bool SOSRingPeerSigUpdate(SOSRingRef ring
, SecKeyRef userPrivKey
, SOSFullPeerInfoRef fpi
,
501 // Returns true if we accepted someone and therefore have to post the ring back to KVS
502 __block
bool result
= false;
503 SecKeyRef userPubKey
= SecKeyCreatePublicFromPrivate(userPrivKey
);
505 // We're going to remove any applicants using a mismatched user key.
506 SOSRingForEachApplicant(ring
, ^(SOSPeerInfoRef peer
) {
507 if(!SOSPeerInfoApplicationVerify(peer
, userPubKey
, NULL
)) {
508 if(!SOSRingRejectRequest(ring
, fpi
, peer
, NULL
)) {
514 result
= SOSRingUpdatePeerInfo(ring
, SOSFullPeerInfoGetPeerInfo(fpi
));
517 SOSRingGenerationSign(ring
, userPrivKey
, fpi
, error
);
518 secnotice("ring", "Generation signed updated signatures on peerinfo");
525 SOSFullPeerInfoRef
SOSRingGetiCloudFullPeerInfoRef(SOSCircleRef circle
, SOSRingRef ring
) {
526 __block SOSFullPeerInfoRef cloud_full_peer
= NULL
;
527 SOSRingForEachActivePeer(circle
, ring
, ^(SOSPeerInfoRef peer
) {
528 if (SOSPeerInfoIsCloudIdentity(peer
)) {
529 if (cloud_full_peer
== NULL
) {
530 CFErrorRef localError
= NULL
;
531 cloud_full_peer
= SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault
, peer
, &localError
);
534 secerror("Found cloud peer in ring but can't make full peer: %@", localError
);
535 CFReleaseNull(localError
);
539 secerror("More than one cloud identity found in ring: %@", ring
);
543 return cloud_full_peer
;
547 CFMutableArrayRef
SOSRingCopyConcurringPeers(SOSRingRef ring
, CFErrorRef
* error
) {
548 SOSRingAssertStable(ring
);
550 CFMutableArrayRef concurringPeers
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
552 if (!SOSRingAppendConcurringPeers(ring
, concurringPeers
, error
))
553 CFReleaseNull(concurringPeers
);
555 return concurringPeers
;
559 bool SOSRingAppendConcurringPeers(SOSCircleRef circle
, SOSRingRef ring
, CFMutableArrayRef appendHere
, CFErrorRef
*error
) {
560 SOSRingForEachActivePeer(circle
, ring
, ^(SOSPeerInfoRef peer
) {
561 CFErrorRef localError
= NULL
;
562 if (SOSRingVerifyPeerSigned(ring
, peer
, &localError
)) {
563 CFArrayAppendValue(appendHere
, peer
);
564 } else if (error
!= NULL
) {
565 secerror("Error checking concurrence: %@", localError
);
567 CFReleaseNull(localError
);