X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/sec/SOSCircle/SecureObjectSync/SOSCircle.c?ds=inline diff --git a/Security/sec/SOSCircle/SecureObjectSync/SOSCircle.c b/Security/sec/SOSCircle/SecureObjectSync/SOSCircle.c new file mode 100644 index 00000000..ff085161 --- /dev/null +++ b/Security/sec/SOSCircle/SecureObjectSync/SOSCircle.c @@ -0,0 +1,1261 @@ +/* + * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +/* + * SOSCircle.c - Implementation of the secure object syncing transport + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +//#include "ckdUtilities.h" + +#include +#include + +#include +#include +#include + +#include +#include + +enum { + kOnlyCompatibleVersion = 1, // Sometime in the future this name will be improved to reflect history. + + kAlwaysIncompatibleVersion = UINT64_MAX, +}; + +struct __OpaqueSOSCircle { + CFRuntimeBase _base; + + CFStringRef name; + CFNumberRef generation; + CFMutableSetRef peers; + CFMutableSetRef applicants; + CFMutableSetRef rejected_applicants; + + CFMutableDictionaryRef signatures; +}; + +CFGiblisWithCompareFor(SOSCircle); + +SOSCircleRef SOSCircleCreate(CFAllocatorRef allocator, CFStringRef name, CFErrorRef *error) { + SOSCircleRef c = CFTypeAllocate(SOSCircle, struct __OpaqueSOSCircle, allocator); + int64_t gen = 1; + + assert(name); + + c->name = CFStringCreateCopy(allocator, name); + c->generation = CFNumberCreate(allocator, kCFNumberSInt64Type, &gen); + c->peers = CFSetCreateMutableForSOSPeerInfosByID(allocator); + c->applicants = CFSetCreateMutableForSOSPeerInfosByID(allocator); + c->rejected_applicants = CFSetCreateMutableForSOSPeerInfosByID(allocator); + c->signatures = CFDictionaryCreateMutableForCFTypes(allocator); + + return c; +} + +static CFNumberRef SOSCircleGenerationCopy(CFNumberRef generation) { + int64_t value; + CFAllocatorRef allocator = CFGetAllocator(generation); + CFNumberGetValue(generation, kCFNumberSInt64Type, &value); + return CFNumberCreate(allocator, kCFNumberSInt64Type, &value); +} + +static CFMutableSetRef CFSetOfPeerInfoDeepCopy(CFAllocatorRef allocator, CFSetRef peerInfoSet) +{ + __block CFMutableSetRef result = CFSetCreateMutableForSOSPeerInfosByID(allocator); + CFSetForEach(peerInfoSet, ^(const void *value) { + SOSPeerInfoRef pi = (SOSPeerInfoRef) value; + CFErrorRef localError = NULL; + SOSPeerInfoRef copiedPeer = SOSPeerInfoCreateCopy(allocator, pi, &localError); + if (copiedPeer) { + CFSetAddValue(result, copiedPeer); + } else { + secerror("Failed to copy peer: %@ (%@)", pi, localError); + } + CFReleaseSafe(copiedPeer); + CFReleaseSafe(localError); + }); + return result; +} + +SOSCircleRef SOSCircleCopyCircle(CFAllocatorRef allocator, SOSCircleRef otherCircle, CFErrorRef *error) +{ + SOSCircleRef c = CFTypeAllocate(SOSCircle, struct __OpaqueSOSCircle, allocator); + + assert(otherCircle); + c->name = CFStringCreateCopy(allocator, otherCircle->name); + c->generation = SOSCircleGenerationCopy(otherCircle->generation); + + c->peers = CFSetOfPeerInfoDeepCopy(allocator, otherCircle->peers); + c->applicants = CFSetOfPeerInfoDeepCopy(allocator, otherCircle->applicants); + c->rejected_applicants = CFSetOfPeerInfoDeepCopy(allocator, otherCircle->rejected_applicants); + + c->signatures = CFDictionaryCreateMutableCopy(allocator, 0, otherCircle->signatures); + + return c; +} + +static inline +void SOSCircleAssertStable(SOSCircleRef circle) +{ + assert(circle); + assert(circle->name); + assert(circle->generation); + assert(circle->peers); + assert(circle->applicants); + assert(circle->rejected_applicants); + assert(circle->signatures); +} + +static inline +SOSCircleRef SOSCircleConvertAndAssertStable(CFTypeRef circleAsType) +{ + if (CFGetTypeID(circleAsType) != SOSCircleGetTypeID()) + return NULL; + + SOSCircleRef circle = (SOSCircleRef) circleAsType; + + SOSCircleAssertStable(circle); + + return circle; +} + +static Boolean SOSCircleCompare(CFTypeRef lhs, CFTypeRef rhs) { + if (CFGetTypeID(lhs) != SOSCircleGetTypeID() + || CFGetTypeID(rhs) != SOSCircleGetTypeID()) + return false; + + SOSCircleRef left = SOSCircleConvertAndAssertStable(lhs); + SOSCircleRef right = SOSCircleConvertAndAssertStable(rhs); + + // TODO: we should be doing set equality for peers and applicants. + return NULL != left && NULL != right + && CFEqualSafe(left->generation, right->generation) + && SOSPeerInfoSetContainsIdenticalPeers(left->peers, right->peers) + && SOSPeerInfoSetContainsIdenticalPeers(left->applicants, right->applicants) + && SOSPeerInfoSetContainsIdenticalPeers(left->rejected_applicants, right->rejected_applicants) + && CFEqualSafe(left->signatures, right->signatures); +} + +static CFMutableArrayRef CFSetCopyValuesCFArray(CFSetRef set) +{ + CFIndex count = CFSetGetCount(set); + + CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + if (count > 0) { + const void * values[count]; + CFSetGetValues(set, values); + for (int current = 0; current < count; ++current) { + CFArrayAppendValue(result, values[current]); + } + } + + return result; +} + +static bool SOSCircleDigestArray(const struct ccdigest_info *di, CFMutableArrayRef array, void *hash_result, CFErrorRef *error) +{ + __block bool success = true; + ccdigest_di_decl(di, array_digest); + const void * a_digest = array_digest; + + ccdigest_init(di, array_digest); + CFArraySortValues(array, CFRangeMake(0, CFArrayGetCount(array)), SOSPeerInfoCompareByID, SOSPeerCmpPubKeyHash); + CFArrayForEach(array, ^(const void *peer) { + if (!SOSPeerInfoUpdateDigestWithPublicKeyBytes((SOSPeerInfoRef)peer, di, a_digest, error)) + success = false; + }); + ccdigest_final(di, array_digest, hash_result); + + return success; +} + +static bool SOSCircleDigestSet(const struct ccdigest_info *di, CFMutableSetRef set, void *hash_result, CFErrorRef *error) +{ + CFMutableArrayRef values = CFSetCopyValuesCFArray(set); + + bool result = SOSCircleDigestArray(di, values, hash_result, error); + + CFReleaseSafe(values); + + return result; +} + + +static bool SOSCircleHash(const struct ccdigest_info *di, SOSCircleRef circle, void *hash_result, CFErrorRef *error) { + ccdigest_di_decl(di, circle_digest); + ccdigest_init(di, circle_digest); + int64_t gen = SOSCircleGetGenerationSint(circle); + ccdigest_update(di, circle_digest, sizeof(gen), &gen); + + SOSCircleDigestSet(di, circle->peers, hash_result, error); + ccdigest_update(di, circle_digest, di->output_size, hash_result); + ccdigest_final(di, circle_digest, hash_result); + return true; +} + +static bool SOSCircleSetSignature(SOSCircleRef circle, SecKeyRef pubkey, CFDataRef signature, CFErrorRef *error) { + bool result = false; + + CFStringRef pubKeyID = SOSCopyIDOfKey(pubkey, error); + require_quiet(pubKeyID, fail); + CFDictionarySetValue(circle->signatures, pubKeyID, signature); + result = true; + +fail: + CFReleaseSafe(pubKeyID); + return result; +} + +static bool SOSCircleRemoveSignatures(SOSCircleRef circle, CFErrorRef *error) { + CFDictionaryRemoveAllValues(circle->signatures); + return true; +} + +static CFDataRef SOSCircleGetSignature(SOSCircleRef circle, SecKeyRef pubkey, CFErrorRef *error) { + CFStringRef pubKeyID = SOSCopyIDOfKey(pubkey, error); + CFDataRef result = NULL; + require_quiet(pubKeyID, fail); + + CFTypeRef value = (CFDataRef)CFDictionaryGetValue(circle->signatures, pubKeyID); + + if (isData(value)) result = (CFDataRef) value; + +fail: + CFReleaseSafe(pubKeyID); + return result; +} + +bool SOSCircleSign(SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) { + if (!privKey) return false; // Really assertion but not always true for now. + CFAllocatorRef allocator = CFGetAllocator(circle); + uint8_t tmp[4096]; + size_t tmplen = 4096; + const struct ccdigest_info *di = ccsha256_di(); + uint8_t hash_result[di->output_size]; + + SOSCircleHash(di, circle, hash_result, error); + OSStatus stat = SecKeyRawSign(privKey, kSecPaddingNone, hash_result, di->output_size, tmp, &tmplen); + if(stat) { + // TODO - Create a CFErrorRef; + secerror("Bad Circle SecKeyRawSign, stat: %ld", (long)stat); + SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Circle SecKeyRawSign"), (error != NULL) ? *error : NULL, error); + return false; + }; + CFDataRef signature = CFDataCreate(allocator, tmp, tmplen); + SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(privKey); + SOSCircleSetSignature(circle, publicKey, signature, error); + CFReleaseNull(publicKey); + CFRelease(signature); + return true; +} + +bool SOSCircleVerifySignatureExists(SOSCircleRef circle, SecKeyRef pubKey, CFErrorRef *error) { + if(!pubKey) { + // TODO ErrorRef + secerror("SOSCircleVerifySignatureExists no pubKey"); + SOSCreateError(kSOSErrorBadFormat, CFSTR("SOSCircleVerifySignatureExists no pubKey"), (error != NULL) ? *error : NULL, error); + return false; + } + CFDataRef signature = SOSCircleGetSignature(circle, pubKey, error); + return NULL != signature; +} + +bool SOSCircleVerify(SOSCircleRef circle, SecKeyRef pubKey, CFErrorRef *error) { + const struct ccdigest_info *di = ccsha256_di(); + uint8_t hash_result[di->output_size]; + + SOSCircleHash(di, circle, hash_result, error); + + CFDataRef signature = SOSCircleGetSignature(circle, pubKey, error); + if(!signature) return false; + + return SecKeyRawVerify(pubKey, kSecPaddingNone, hash_result, di->output_size, + CFDataGetBytePtr(signature), CFDataGetLength(signature)) == errSecSuccess; +} + +bool SOSCircleVerifyPeerSigned(SOSCircleRef circle, SOSPeerInfoRef peer, CFErrorRef *error) { + SecKeyRef pub_key = SOSPeerInfoCopyPubKey(peer); + bool result = SOSCircleVerify(circle, pub_key, error); + CFReleaseSafe(pub_key); + return result; +} + +static void CFSetRemoveAllPassing(CFMutableSetRef set, bool (^test)(const void *) ){ + CFMutableArrayRef toBeRemoved = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + + CFSetForEach(set, ^(const void *value) { + if (test(value)) + CFArrayAppendValue(toBeRemoved, value); + }); + + CFArrayForEach(toBeRemoved, ^(const void *value) { + CFSetRemoveValue(set, value); + }); + CFReleaseNull(toBeRemoved); +} + +static void SOSCircleRejectNonValidApplicants(SOSCircleRef circle, SecKeyRef pubkey) { + CFMutableSetRef applicants = SOSCircleCopyApplicants(circle, NULL); + CFSetForEach(applicants, ^(const void *value) { + SOSPeerInfoRef pi = (SOSPeerInfoRef) value; + if(!SOSPeerInfoApplicationVerify(pi, pubkey, NULL)) { + CFSetRemoveValue(circle->applicants, pi); + CFSetSetValue(circle->rejected_applicants, pi); + } + }); + CFReleaseNull(applicants); +} + +static SOSPeerInfoRef SOSCircleCopyPeerInfo(SOSCircleRef circle, CFStringRef peer_id, CFErrorRef *error) { + __block SOSPeerInfoRef result = NULL; + + CFSetForEach(circle->peers, ^(const void *value) { + if (result == NULL) { + SOSPeerInfoRef tpi = (SOSPeerInfoRef)value; + if (CFEqual(SOSPeerInfoGetPeerID(tpi), peer_id)) + result = tpi; + } + }); + + CFRetainSafe(result); + return result; +} + +static bool SOSCircleUpgradePeerInfo(SOSCircleRef circle, SecKeyRef user_approver, SOSFullPeerInfoRef peerinfo) { + bool retval = false; + SecKeyRef userPubKey = SecKeyCreatePublicFromPrivate(user_approver); + SOSPeerInfoRef fpi_pi = SOSFullPeerInfoGetPeerInfo(peerinfo); + SOSPeerInfoRef pi = SOSCircleCopyPeerInfo(circle, SOSPeerInfoGetPeerID(fpi_pi), NULL); + require_quiet(pi, out); + require_quiet(SOSPeerInfoApplicationVerify(pi, userPubKey, NULL), re_sign); + CFReleaseNull(userPubKey); + CFReleaseNull(pi); + return true; + +re_sign: + secerror("SOSCircleGenerationSign: Upgraded peer's Application Signature"); + SecKeyRef device_key = SOSFullPeerInfoCopyDeviceKey(peerinfo, NULL); + require_quiet(device_key, out); + SOSPeerInfoRef new_pi = SOSPeerInfoCopyAsApplication(pi, user_approver, device_key, NULL); + if(SOSCircleUpdatePeerInfo(circle, new_pi)) + retval = true; + CFReleaseNull(new_pi); + CFReleaseNull(device_key); +out: + CFReleaseNull(userPubKey); + CFReleaseNull(pi); + return retval; +} + +bool SOSCircleGenerationSign(SOSCircleRef circle, SecKeyRef user_approver, SOSFullPeerInfoRef peerinfo, CFErrorRef *error) { + SecKeyRef ourKey = SOSFullPeerInfoCopyDeviceKey(peerinfo, error); + SecKeyRef publicKey = NULL; + require_quiet(ourKey, fail); + + // Check if we're using an invalid peerinfo for this op. There are cases where we might not be "upgraded". + require_quiet(SOSCircleUpgradePeerInfo(circle, user_approver, peerinfo), fail); + SOSCircleRemoveRetired(circle, error); // Prune off retirees since we're signing this one + CFSetRemoveAllValues(circle->rejected_applicants); // Dump rejects so we clean them up sometime. + publicKey = SecKeyCreatePublicFromPrivate(user_approver); + SOSCircleRejectNonValidApplicants(circle, publicKey); + SOSCircleGenerationIncrement(circle); + require_quiet(SOSCircleRemoveSignatures(circle, error), fail); + require_quiet(SOSCircleSign(circle, user_approver, error), fail); + require_quiet(SOSCircleSign(circle, ourKey, error), fail); + + CFReleaseNull(ourKey); + CFReleaseNull(publicKey); + return true; + +fail: + CFReleaseNull(ourKey); + CFReleaseNull(publicKey); + return false; +} + +bool SOSCircleGenerationUpdate(SOSCircleRef circle, SecKeyRef user_approver, SOSFullPeerInfoRef peerinfo, CFErrorRef *error) { + + return SOSCircleGenerationSign(circle, user_approver, peerinfo, error); + +#if 0 + bool success = false; + + SecKeyRef ourKey = SOSFullPeerInfoCopyDeviceKey(peerinfo, error); + require_quiet(ourKey, fail); + + require_quiet(SOSCircleSign(circle, user_approver, error), fail); + require_quiet(SOSCircleSign(circle, ourKey, error), fail); + + success = true; + +fail: + CFReleaseNull(ourKey); + return success; +#endif +} + +bool SOSCircleConcordanceSign(SOSCircleRef circle, SOSFullPeerInfoRef peerinfo, CFErrorRef *error) { + bool success = false; + SecKeyRef ourKey = SOSFullPeerInfoCopyDeviceKey(peerinfo, error); + require_quiet(ourKey, exit); + + success = SOSCircleSign(circle, ourKey, error); + +exit: + CFReleaseNull(ourKey); + return success; +} + +static inline SOSConcordanceStatus CheckPeerStatus(SOSCircleRef circle, SOSPeerInfoRef peer, SecKeyRef user_public_key, CFErrorRef *error) { + SOSConcordanceStatus result = kSOSConcordanceNoPeer; + SecKeyRef pubKey = SOSPeerInfoCopyPubKey(peer); + + require_action_quiet(SOSCircleHasActiveValidPeer(circle, peer, user_public_key, error), exit, result = kSOSConcordanceNoPeer); + require_action_quiet(SOSCircleVerifySignatureExists(circle, pubKey, error), exit, result = kSOSConcordanceNoPeerSig); + require_action_quiet(SOSCircleVerify(circle, pubKey, error), exit, result = kSOSConcordanceBadPeerSig); + + result = kSOSConcordanceTrusted; + +exit: + CFReleaseNull(pubKey); + return result; +} + +static inline SOSConcordanceStatus CombineStatus(SOSConcordanceStatus status1, SOSConcordanceStatus status2) +{ + if (status1 == kSOSConcordanceTrusted || status2 == kSOSConcordanceTrusted) + return kSOSConcordanceTrusted; + + if (status1 == kSOSConcordanceBadPeerSig || status2 == kSOSConcordanceBadPeerSig) + return kSOSConcordanceBadPeerSig; + + if (status1 == kSOSConcordanceNoPeerSig || status2 == kSOSConcordanceNoPeerSig) + return kSOSConcordanceNoPeerSig; + + return status1; +} + +static inline bool SOSCircleIsEmpty(SOSCircleRef circle) { + return SOSCircleCountPeers(circle) == 0; +} + +static inline bool SOSCircleIsOffering(SOSCircleRef circle) { + return SOSCircleCountPeers(circle) == 1; +} + +__unused static inline bool SOSCircleIsResignOffering(SOSCircleRef circle, SecKeyRef pubkey) { + return SOSCircleCountActiveValidPeers(circle, pubkey) == 1; +} + +static inline SOSConcordanceStatus GetSignersStatus(SOSCircleRef signers_circle, SOSCircleRef status_circle, + SecKeyRef user_pubKey, SOSPeerInfoRef exclude, CFErrorRef *error) { + CFStringRef excluded_id = exclude ? SOSPeerInfoGetPeerID(exclude) : NULL; + + __block SOSConcordanceStatus status = kSOSConcordanceNoPeer; + SOSCircleForEachActivePeer(signers_circle, ^(SOSPeerInfoRef peer) { + SOSConcordanceStatus peerStatus = CheckPeerStatus(status_circle, peer, user_pubKey, error); + + if (peerStatus == kSOSConcordanceNoPeerSig && + (CFEqualSafe(SOSPeerInfoGetPeerID(peer), excluded_id) || SOSPeerInfoIsCloudIdentity(peer))) + peerStatus = kSOSConcordanceNoPeer; + + status = CombineStatus(status, peerStatus); // TODO: Use multiple error gathering. + }); + + return status; +} + +static inline bool isOlderGeneration(SOSCircleRef current, SOSCircleRef proposed) { + return CFNumberCompare(current->generation, proposed->generation, NULL) == kCFCompareGreaterThan; +} + +bool SOSCircleSharedTrustedPeers(SOSCircleRef current, SOSCircleRef proposed, SOSPeerInfoRef me) { + __block bool retval = false; + SOSCircleForEachPeer(current, ^(SOSPeerInfoRef peer) { + if(!CFEqual(me, peer) && SOSCircleHasPeer(proposed, peer, NULL)) retval = true; + }); + return retval; +} + +static void SOSCircleUpgradePeersByCircle(SOSCircleRef known_circle, SOSCircleRef proposed_circle) { + SOSCircleForEachPeer(known_circle, ^(SOSPeerInfoRef known_peer) { + SOSPeerInfoRef proposed_peer = SOSCircleCopyPeerInfo(proposed_circle, SOSPeerInfoGetPeerID(known_peer), NULL); + if(proposed_peer && CFEqualSafe(proposed_peer, known_peer) != 0) { + SOSCircleUpdatePeerInfo(known_circle, proposed_peer); + } + }); +} + +SOSConcordanceStatus SOSCircleConcordanceTrust(SOSCircleRef known_circle, SOSCircleRef proposed_circle, + SecKeyRef known_pubkey, SecKeyRef user_pubkey, + SOSPeerInfoRef exclude, CFErrorRef *error) { + if(user_pubkey == NULL) { + SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Concordance with no public key"), NULL, error); + return kSOSConcordanceNoUserKey; //TODO: - needs to return an error + } + + if (SOSCircleIsEmpty(proposed_circle)) { + return kSOSConcordanceTrusted; + } + + if(!SOSCircleVerifySignatureExists(proposed_circle, user_pubkey, error)) { + SOSCreateError(kSOSErrorBadSignature, CFSTR("No public signature"), (error != NULL) ? *error : NULL, error); + return kSOSConcordanceNoUserSig; + } + + if(!SOSCircleVerify(proposed_circle, user_pubkey, error)) { + SOSCreateError(kSOSErrorBadSignature, CFSTR("Bad public signature"), (error != NULL) ? *error : NULL, error); + return kSOSConcordanceBadUserSig; + } + + if (SOSCircleIsEmpty(known_circle) || SOSCircleIsOffering(proposed_circle)) { + return GetSignersStatus(proposed_circle, proposed_circle, user_pubkey, NULL, error); + } + + if(isOlderGeneration(known_circle, proposed_circle)) { + SOSCreateError(kSOSErrorReplay, CFSTR("Bad generation"), NULL, error); + return kSOSConcordanceGenOld; + } + + + if(!SOSCircleVerify(known_circle, user_pubkey, error)) { + SOSCircleUpgradePeersByCircle(known_circle, proposed_circle); + } + + if(known_pubkey == NULL) known_pubkey = user_pubkey; + if(!SOSCircleVerify(known_circle, known_pubkey, error)) known_pubkey = user_pubkey; + return GetSignersStatus(known_circle, proposed_circle, known_pubkey, exclude, error); +} + + +static const uint8_t* der_decode_mutable_dictionary(CFAllocatorRef allocator, CFOptionFlags mutability, + CFMutableDictionaryRef* dictionary, CFErrorRef *error, + const uint8_t* der, const uint8_t *der_end) +{ + CFDictionaryRef theDict; + const uint8_t* result = der_decode_dictionary(allocator, mutability, &theDict, error, der, der_end); + + if (result != NULL) + *dictionary = (CFMutableDictionaryRef)theDict; + + return result; +} + + +SOSCircleRef SOSCircleCreateFromDER(CFAllocatorRef allocator, CFErrorRef* error, + const uint8_t** der_p, const uint8_t *der_end) { + SOSCircleRef cir = CFTypeAllocate(SOSCircle, struct __OpaqueSOSCircle, allocator); + + const uint8_t *sequence_end; + + cir->name = NULL; + cir->generation = NULL; + cir->peers = NULL; + cir->applicants = NULL; + cir->rejected_applicants = NULL; + cir->signatures = NULL; + + *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end); + require_action_quiet(sequence_end != NULL, fail, + SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Circle DER"), (error != NULL) ? *error : NULL, error)); + + // Version first. + uint64_t version = 0; + *der_p = ccder_decode_uint64(&version, *der_p, der_end); + + require_action_quiet(version == kOnlyCompatibleVersion, fail, + SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Bad Circle Version"), NULL, error)); + + *der_p = der_decode_string(allocator, 0, &cir->name, error, *der_p, sequence_end); + *der_p = der_decode_number(allocator, 0, &cir->generation, error, *der_p, sequence_end); + + cir->peers = SOSPeerInfoSetCreateFromArrayDER(allocator, &kSOSPeerSetCallbacks, error, der_p, sequence_end); + cir->applicants = SOSPeerInfoSetCreateFromArrayDER(allocator, &kSOSPeerSetCallbacks, error, der_p, sequence_end); + cir->rejected_applicants = SOSPeerInfoSetCreateFromArrayDER(allocator, &kSOSPeerSetCallbacks, error, der_p, sequence_end); + + *der_p = der_decode_mutable_dictionary(allocator, kCFPropertyListMutableContainersAndLeaves, + &cir->signatures, error, *der_p, sequence_end); + + require_action_quiet(*der_p == sequence_end, fail, + SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Circle DER"), (error != NULL) ? *error : NULL, error)); + + return cir; + +fail: + CFReleaseNull(cir); + return NULL; +} + +SOSCircleRef SOSCircleCreateFromData(CFAllocatorRef allocator, CFDataRef circleData, CFErrorRef *error) +{ + size_t size = CFDataGetLength(circleData); + const uint8_t *der = CFDataGetBytePtr(circleData); + SOSCircleRef inflated = SOSCircleCreateFromDER(allocator, error, &der, der + size); + return inflated; +} + +size_t SOSCircleGetDEREncodedSize(SOSCircleRef cir, CFErrorRef *error) { + SOSCircleAssertStable(cir); + size_t total_payload = 0; + + require_quiet(accumulate_size(&total_payload, ccder_sizeof_uint64(kOnlyCompatibleVersion)), fail); + require_quiet(accumulate_size(&total_payload, der_sizeof_string(cir->name, error)), fail); + require_quiet(accumulate_size(&total_payload, der_sizeof_number(cir->generation, error)), fail); + require_quiet(accumulate_size(&total_payload, SOSPeerInfoSetGetDEREncodedArraySize(cir->peers, error)), fail); + require_quiet(accumulate_size(&total_payload, SOSPeerInfoSetGetDEREncodedArraySize(cir->applicants, error)), fail); + require_quiet(accumulate_size(&total_payload, SOSPeerInfoSetGetDEREncodedArraySize(cir->rejected_applicants, error)), fail); + require_quiet(accumulate_size(&total_payload, der_sizeof_dictionary((CFDictionaryRef) cir->signatures, error)), fail); + + return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, total_payload); + +fail: + SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error); + return 0; +} + +uint8_t* SOSCircleEncodeToDER(SOSCircleRef cir, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) { + SOSCircleAssertStable(cir); + + return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, + ccder_encode_uint64(kOnlyCompatibleVersion, der, + der_encode_string(cir->name, error, der, + der_encode_number(cir->generation, error, der, + SOSPeerInfoSetEncodeToArrayDER(cir->peers, error, der, + SOSPeerInfoSetEncodeToArrayDER(cir->applicants, error, der, + SOSPeerInfoSetEncodeToArrayDER(cir->rejected_applicants, error, der, + der_encode_dictionary((CFDictionaryRef) cir->signatures, error, der, der_end)))))))); +} + +CFDataRef SOSCircleCreateIncompatibleCircleDER(CFErrorRef* error) +{ + size_t total_payload = 0; + size_t encoded_size = 0; + uint8_t* der = 0; + uint8_t* der_end = 0; + CFMutableDataRef result = NULL; + + require_quiet(accumulate_size(&total_payload, ccder_sizeof_uint64(kAlwaysIncompatibleVersion)), fail); + + encoded_size = ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, total_payload); + + result = CFDataCreateMutableWithScratch(kCFAllocatorDefault, encoded_size); + + der = CFDataGetMutableBytePtr(result); + der_end = der + CFDataGetLength(result); + + der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, + ccder_encode_uint64(kAlwaysIncompatibleVersion, der, der_end)); + +fail: + if (der == NULL || der != der_end) + CFReleaseNull(result); + + return result; +} + + +CFDataRef SOSCircleCopyEncodedData(SOSCircleRef circle, CFAllocatorRef allocator, CFErrorRef *error) +{ + size_t size = SOSCircleGetDEREncodedSize(circle, error); + if (size == 0) + return NULL; + uint8_t buffer[size]; + uint8_t* start = SOSCircleEncodeToDER(circle, error, buffer, buffer + sizeof(buffer)); + CFDataRef result = CFDataCreate(kCFAllocatorDefault, start, size); + return result; +} + +static void SOSCircleDestroy(CFTypeRef aObj) { + SOSCircleRef c = (SOSCircleRef) aObj; + + CFReleaseNull(c->name); + CFReleaseNull(c->generation); + CFReleaseNull(c->peers); + CFReleaseNull(c->applicants); + CFReleaseNull(c->rejected_applicants); + CFReleaseNull(c->signatures); +} + +static CFStringRef SOSCircleCopyDescription(CFTypeRef aObj) { + SOSCircleRef c = (SOSCircleRef) aObj; + + SOSCircleAssertStable(c); + + + CFMutableStringRef description = CFStringCreateMutable(kCFAllocatorDefault, 0); + CFStringAppendFormat(description, NULL, CFSTR("name); + + __block CFStringRef separator = CFSTR(""); + SOSCircleForEachPeer(c, ^(SOSPeerInfoRef peer) { + CFStringRef sig = NULL; + if (SOSCircleVerifyPeerSigned(c, peer, NULL)) { + sig = CFSTR("√"); + } else { + SecKeyRef pub_key = SOSPeerInfoCopyPubKey(peer); + CFDataRef signature = SOSCircleGetSignature(c, pub_key, NULL); + sig = (signature == NULL) ? CFSTR("-") : CFSTR("?"); + CFReleaseNull(pub_key); + } + + CFStringAppendFormat(description, NULL, CFSTR("%@%@ %@"), separator, peer, sig); + separator = CFSTR(","); + }); + + CFStringAppend(description, CFSTR("], A:[")); + separator = CFSTR(""); + SOSCircleForEachApplicant(c, ^(SOSPeerInfoRef peer) { + CFStringAppendFormat(description, NULL, CFSTR("%@%@"), separator, peer); + separator = CFSTR(","); + }); + + CFStringAppend(description, CFSTR("], R:[")); + separator = CFSTR(""); + CFSetForEach(c->rejected_applicants, ^(const void *value) { + SOSPeerInfoRef peer = (SOSPeerInfoRef) value; + CFStringAppendFormat(description, NULL, CFSTR("%@%@"), separator, peer); + separator = CFSTR(","); + }); + CFStringAppend(description, CFSTR("]>")); + + return description; +} + +CFStringRef SOSCircleGetName(SOSCircleRef circle) { + assert(circle); + assert(circle->name); + return circle->name; +} + +const char *SOSCircleGetNameC(SOSCircleRef circle) { + CFStringRef name = SOSCircleGetName(circle); + if (!name) + return strdup(""); + return CFStringToCString(name); +} + +CFNumberRef SOSCircleGetGeneration(SOSCircleRef circle) { + assert(circle); + assert(circle->generation); + return circle->generation; +} + +int64_t SOSCircleGetGenerationSint(SOSCircleRef circle) { + CFNumberRef gen = SOSCircleGetGeneration(circle); + int64_t value; + if(!gen) return 0; + CFNumberGetValue(gen, kCFNumberSInt64Type, &value); + return value; +} + +void SOSCircleGenerationSetValue(SOSCircleRef circle, int64_t value) +{ + CFAllocatorRef allocator = CFGetAllocator(circle->generation); + CFAssignRetained(circle->generation, CFNumberCreate(allocator, kCFNumberSInt64Type, &value)); +} + + +static int64_t GenerationSetHighBits(int64_t value, int32_t high_31) +{ + value &= 0xFFFFFFFF; // Keep the low 32 bits. + value |= ((int64_t) high_31) << 32; + + return value; +} + + +void SOSCircleGenerationIncrement(SOSCircleRef circle) { + int64_t value = SOSCircleGetGenerationSint(circle); + + if ((value >> 32) == 0) { + uint32_t seconds = CFAbsoluteTimeGetCurrent(); // seconds + value = GenerationSetHighBits(value, (seconds >> 1)); + } + + value++; + + SOSCircleGenerationSetValue(circle, value); +} + +int SOSCircleCountPeers(SOSCircleRef circle) { + SOSCircleAssertStable(circle); + __block int count = 0; + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + ++count; + }); + return count; +} + +int SOSCircleCountActivePeers(SOSCircleRef circle) { + SOSCircleAssertStable(circle); + __block int count = 0; + SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { + ++count; + }); + return count; +} + +int SOSCircleCountActiveValidPeers(SOSCircleRef circle, SecKeyRef pubkey) { + SOSCircleAssertStable(circle); + __block int count = 0; + SOSCircleForEachActiveValidPeer(circle, pubkey, ^(SOSPeerInfoRef peer) { + ++count; + }); + return count; +} + +int SOSCircleCountRetiredPeers(SOSCircleRef circle) { + SOSCircleAssertStable(circle); + __block int count = 0; + SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) { + ++count; + }); + return count; +} + +int SOSCircleCountApplicants(SOSCircleRef circle) { + SOSCircleAssertStable(circle); + + return (int)CFSetGetCount(circle->applicants); +} + +bool SOSCircleHasApplicant(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + + return CFSetContainsValue(circle->applicants, peerInfo); +} + +CFMutableSetRef SOSCircleCopyApplicants(SOSCircleRef circle, CFAllocatorRef allocator) { + SOSCircleAssertStable(circle); + + return CFSetCreateMutableCopy(allocator, 0, circle->applicants); +} + +int SOSCircleCountRejectedApplicants(SOSCircleRef circle) { + SOSCircleAssertStable(circle); + + return (int)CFSetGetCount(circle->rejected_applicants); +} + +bool SOSCircleHasRejectedApplicant(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + return CFSetContainsValue(circle->rejected_applicants, peerInfo); +} + +SOSPeerInfoRef SOSCircleCopyRejectedApplicant(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + return CFRetainSafe((SOSPeerInfoRef)CFSetGetValue(circle->rejected_applicants, peerInfo)); +} + +CFMutableArrayRef SOSCircleCopyRejectedApplicants(SOSCircleRef circle, CFAllocatorRef allocator) { + SOSCircleAssertStable(circle); + + return CFSetCopyValuesCFArray(circle->rejected_applicants); +} + + +bool SOSCircleHasPeerWithID(SOSCircleRef circle, CFStringRef peerid, CFErrorRef *error) { + SOSCircleAssertStable(circle); + __block bool found = false; + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + if(peerid && peer && CFEqualSafe(peerid, SOSPeerInfoGetPeerID(peer))) found = true; + }); + return found; +} + +SOSPeerInfoRef SOSCircleCopyPeerWithID(SOSCircleRef circle, CFStringRef peerid, CFErrorRef *error) { + SOSCircleAssertStable(circle); + __block SOSPeerInfoRef found = false; + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + if(peerid && peer && CFEqualSafe(peerid, SOSPeerInfoGetPeerID(peer))) found = peer; + }); + return found; +} + +bool SOSCircleHasPeer(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + return SOSCircleHasPeerWithID(circle, SOSPeerInfoGetPeerID(peerInfo), error); +} + +bool SOSCircleHasActivePeerWithID(SOSCircleRef circle, CFStringRef peerid, CFErrorRef *error) { + SOSCircleAssertStable(circle); + __block bool found = false; + SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { + if(peerid && peer && CFEqualSafe(peerid, SOSPeerInfoGetPeerID(peer))) found = true; + }); + return found; +} + +bool SOSCircleHasActivePeer(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + if(!peerInfo) return false; + return SOSCircleHasActivePeerWithID(circle, SOSPeerInfoGetPeerID(peerInfo), error); +} + +bool SOSCircleHasActiveValidPeerWithID(SOSCircleRef circle, CFStringRef peerid, SecKeyRef user_public_key, CFErrorRef *error) { + SOSCircleAssertStable(circle); + __block bool found = false; + SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { + if(peerid && peer && CFEqualSafe(peerid, SOSPeerInfoGetPeerID(peer)) && SOSPeerInfoApplicationVerify(peer, user_public_key, NULL)) found = true; + }); + return found; +} + +bool SOSCircleHasActiveValidPeer(SOSCircleRef circle, SOSPeerInfoRef peerInfo, SecKeyRef user_public_key, CFErrorRef *error) { + if(!peerInfo) return false; + return SOSCircleHasActiveValidPeerWithID(circle, SOSPeerInfoGetPeerID(peerInfo), user_public_key, error); +} + + +bool SOSCircleResetToEmpty(SOSCircleRef circle, CFErrorRef *error) { + CFSetRemoveAllValues(circle->applicants); + CFSetRemoveAllValues(circle->rejected_applicants); + CFSetRemoveAllValues(circle->peers); + CFDictionaryRemoveAllValues(circle->signatures); + + int64_t old_value = SOSCircleGetGenerationSint(circle); + + SOSCircleGenerationSetValue(circle, 0); + SOSCircleGenerationIncrement(circle); // Increment to get high bits set. + + int64_t new_value = SOSCircleGetGenerationSint(circle); + + if (new_value <= old_value) { + int64_t new_new_value = GenerationSetHighBits(new_value, (old_value >> 32) + 1); + SOSCircleGenerationSetValue(circle, new_new_value); + } + + return true; +} + +bool SOSCircleResetToOffering(SOSCircleRef circle, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error){ + + return SOSCircleResetToEmpty(circle, error) + && SOSCircleRequestAdmission(circle, user_privkey, requestor, error) + && SOSCircleAcceptRequest(circle, user_privkey, requestor, SOSFullPeerInfoGetPeerInfo(requestor), error); +} + +bool SOSCircleRemoveRetired(SOSCircleRef circle, CFErrorRef *error) { + CFSetRemoveAllPassing(circle->peers, ^ bool (const void *element) { + SOSPeerInfoRef peer = (SOSPeerInfoRef) element; + + return SOSPeerInfoIsRetirementTicket(peer); + }); + + return true; +} + +static bool SOSCircleRecordAdmissionRequest(SOSCircleRef circle, SecKeyRef user_pubkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) { + SOSCircleAssertStable(circle); + + SOSPeerInfoRef requestorPeerInfo = SOSFullPeerInfoGetPeerInfo(requestor); + bool isPeer = SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(requestor), error); + + require_action_quiet(!isPeer, fail, SOSCreateError(kSOSErrorAlreadyPeer, CFSTR("Cannot request admission when already a peer"), NULL, error)); + + CFSetRemoveValue(circle->rejected_applicants, requestorPeerInfo); // We remove from rejected list, in case? + CFSetSetValue(circle->applicants, requestorPeerInfo); + + return true; + +fail: + return false; + +} + +bool SOSCircleRequestReadmission(SOSCircleRef circle, SecKeyRef user_pubkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) { + bool success = false; + + SOSPeerInfoRef peer = SOSFullPeerInfoGetPeerInfo(requestor); + require_quiet(SOSPeerInfoApplicationVerify(peer, user_pubkey, error), fail); + success = SOSCircleRecordAdmissionRequest(circle, user_pubkey, requestor, error); +fail: + return success; +} + +bool SOSCircleRequestAdmission(SOSCircleRef circle, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) { + bool success = false; + + SecKeyRef user_pubkey = SecKeyCreatePublicFromPrivate(user_privkey); + require_action_quiet(user_pubkey, fail, SOSCreateError(kSOSErrorBadKey, CFSTR("No public key for key"), NULL, error)); + + require(SOSFullPeerInfoPromoteToApplication(requestor, user_privkey, error), fail); + + success = SOSCircleRecordAdmissionRequest(circle, user_pubkey, requestor, error); +fail: + CFReleaseNull(user_pubkey); + return success; +} + + +bool SOSCircleUpdatePeerInfo(SOSCircleRef circle, SOSPeerInfoRef replacement_peer_info) { + if(!replacement_peer_info) return false; + CFTypeRef old = CFSetGetValue(circle->peers, replacement_peer_info); + bool replace = old && !CFEqualSafe(old, replacement_peer_info); + if (replace) + CFSetReplaceValue(circle->peers, replacement_peer_info); + + return replace; +} + +bool SOSCircleRemovePeer(SOSCircleRef circle, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, SOSPeerInfoRef peer_to_remove, CFErrorRef *error) { + SOSPeerInfoRef requestor_peer_info = SOSFullPeerInfoGetPeerInfo(requestor); + + if (SOSCircleHasApplicant(circle, peer_to_remove, error)) { + return SOSCircleRejectRequest(circle, requestor, peer_to_remove, error); + } + + if (!SOSCircleHasPeer(circle, requestor_peer_info, error)) { + SOSCreateError(kSOSErrorAlreadyPeer, CFSTR("Must be peer to remove peer"), NULL, error); + return false; + } + + CFSetRemoveValue(circle->peers, peer_to_remove); + + SOSCircleGenerationSign(circle, user_privkey, requestor, error); + + return true; +} + +bool SOSCircleAcceptRequest(SOSCircleRef circle, SecKeyRef user_privkey, SOSFullPeerInfoRef device_approver, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + + SecKeyRef publicKey = NULL; + bool result = false; + + require_action_quiet(CFSetContainsValue(circle->applicants, peerInfo), fail, + SOSCreateError(kSOSErrorNotApplicant, CFSTR("Cannot accept non-applicant"), NULL, error)); + + publicKey = SecKeyCreatePublicFromPrivate(user_privkey); + require_quiet(SOSPeerInfoApplicationVerify(peerInfo, publicKey, error), fail); + + CFSetRemoveValue(circle->applicants, peerInfo); + CFSetSetValue(circle->peers, peerInfo); + + result = SOSCircleGenerationSign(circle, user_privkey, device_approver, error); + secnotice("circle", "Accepted %@", peerInfo); + +fail: + CFReleaseNull(publicKey); + return result; +} + +bool SOSCircleWithdrawRequest(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + + CFSetRemoveValue(circle->applicants, peerInfo); + + return true; +} + +bool SOSCircleRemoveRejectedPeer(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + + CFSetRemoveValue(circle->rejected_applicants, peerInfo); + + return true; +} + + +bool SOSCircleRejectRequest(SOSCircleRef circle, SOSFullPeerInfoRef device_rejector, + SOSPeerInfoRef peerInfo, CFErrorRef *error) { + SOSCircleAssertStable(circle); + + if (CFEqual(SOSPeerInfoGetPeerID(peerInfo), SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(device_rejector)))) + return SOSCircleWithdrawRequest(circle, peerInfo, error); + + if (!CFSetContainsValue(circle->applicants, peerInfo)) { + SOSCreateError(kSOSErrorNotApplicant, CFSTR("Cannot reject non-applicant"), NULL, error); + return false; + } + + CFSetRemoveValue(circle->applicants, peerInfo); + CFSetSetValue(circle->rejected_applicants, peerInfo); + + // TODO: Maybe we sign the rejection with device_rejector. + + return true; +} + +bool SOSCircleAcceptRequests(SOSCircleRef circle, SecKeyRef user_privkey, SOSFullPeerInfoRef device_approver, + CFErrorRef *error) { + // Returns true if we accepted someone and therefore have to post the circle back to KVS + __block bool result = false; + + SOSCircleForEachApplicant(circle, ^(SOSPeerInfoRef peer) { + if (!SOSCircleAcceptRequest(circle, user_privkey, device_approver, peer, error)) + printf("error in SOSCircleAcceptRequest\n"); + else { + secnotice("circle", "Accepted peer: %@", peer); + result = true; + } + }); + + if (result) { + SOSCircleGenerationSign(circle, user_privkey, device_approver, error); + secnotice("circle", "Countersigned accepted requests"); + } + + return result; +} + +bool SOSCirclePeerSigUpdate(SOSCircleRef circle, SecKeyRef userPrivKey, SOSFullPeerInfoRef fpi, + CFErrorRef *error) { + // Returns true if we accepted someone and therefore have to post the circle back to KVS + __block bool result = false; + SecKeyRef userPubKey = SecKeyCreatePublicFromPrivate(userPrivKey); + + // We're going to remove any applicants using a mismatched user key. + SOSCircleForEachApplicant(circle, ^(SOSPeerInfoRef peer) { + if(!SOSPeerInfoApplicationVerify(peer, userPubKey, NULL)) { + if(!SOSCircleRejectRequest(circle, fpi, peer, NULL)) { + // do we care? + } + } + }); + + result = SOSCircleUpdatePeerInfo(circle, SOSFullPeerInfoGetPeerInfo(fpi)); + + if (result) { + SOSCircleGenerationSign(circle, userPrivKey, fpi, error); + secnotice("circle", "Generation signed updated signatures on peerinfo"); + } + + return result; +} + +static inline void SOSCircleForEachPeerMatching(SOSCircleRef circle, + void (^action)(SOSPeerInfoRef peer), + bool (^condition)(SOSPeerInfoRef peer)) { + CFSetForEach(circle->peers, ^(const void *value) { + SOSPeerInfoRef peer = (SOSPeerInfoRef) value; + if (condition(peer)) + action(peer); + }); +} + +static inline bool isHiddenPeer(SOSPeerInfoRef peer) { + return SOSPeerInfoIsRetirementTicket(peer) || SOSPeerInfoIsCloudIdentity(peer); +} + +void SOSCircleForEachPeer(SOSCircleRef circle, void (^action)(SOSPeerInfoRef peer)) { + SOSCircleForEachPeerMatching(circle, action, ^bool(SOSPeerInfoRef peer) { + return !isHiddenPeer(peer); + }); +} + +void SOSCircleForEachRetiredPeer(SOSCircleRef circle, void (^action)(SOSPeerInfoRef peer)) { + SOSCircleForEachPeerMatching(circle, action, ^bool(SOSPeerInfoRef peer) { + return SOSPeerInfoIsRetirementTicket(peer); + }); +} + +void SOSCircleForEachActivePeer(SOSCircleRef circle, void (^action)(SOSPeerInfoRef peer)) { + SOSCircleForEachPeerMatching(circle, action, ^bool(SOSPeerInfoRef peer) { + return true; + }); +} + +void SOSCircleForEachActiveValidPeer(SOSCircleRef circle, SecKeyRef user_public_key, void (^action)(SOSPeerInfoRef peer)) { + SOSCircleForEachPeerMatching(circle, action, ^bool(SOSPeerInfoRef peer) { + return SOSPeerInfoApplicationVerify(peer, user_public_key, NULL); + }); +} + +void SOSCircleForEachApplicant(SOSCircleRef circle, void (^action)(SOSPeerInfoRef peer)) { + CFSetForEach(circle->applicants, ^(const void*value) { action((SOSPeerInfoRef) value); } ); +} + + +CFMutableSetRef SOSCircleCopyPeers(SOSCircleRef circle, CFAllocatorRef allocator) { + SOSCircleAssertStable(circle); + + CFMutableSetRef result = CFSetCreateMutableForSOSPeerInfosByID(allocator); + + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + CFSetAddValue(result, peer); + }); + + return result; +} + +bool SOSCircleAppendConcurringPeers(SOSCircleRef circle, CFMutableArrayRef appendHere, CFErrorRef *error) { + SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { + CFErrorRef localError = NULL; + if (SOSCircleVerifyPeerSigned(circle, peer, &localError)) { + CFArrayAppendValue(appendHere, peer); + } else if (error != NULL) { + secerror("Error checking concurrence: %@", localError); + } + CFReleaseNull(localError); + }); + + return true; +} + +CFMutableArrayRef SOSCircleCopyConcurringPeers(SOSCircleRef circle, CFErrorRef* error) { + SOSCircleAssertStable(circle); + + CFMutableArrayRef concurringPeers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + if (!SOSCircleAppendConcurringPeers(circle, concurringPeers, error)) + CFReleaseNull(concurringPeers); + + return concurringPeers; +} + +SOSFullPeerInfoRef SOSCircleGetiCloudFullPeerInfoRef(SOSCircleRef circle) { + __block SOSFullPeerInfoRef cloud_full_peer = NULL; + SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { + if (SOSPeerInfoIsCloudIdentity(peer)) { + if (cloud_full_peer == NULL) { + CFErrorRef localError = NULL; + cloud_full_peer = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, &localError); + + if (localError) { + secerror("Found cloud peer in circle but can't make full peer: %@", localError); + CFReleaseNull(localError); + } + + } else { + secerror("More than one cloud identity found in circle: %@", circle); + } + } + }); + return cloud_full_peer; +}