#include <AssertMacros.h>
#include <CoreFoundation/CFArray.h>
#include <SecureObjectSync/SOSCircle.h>
#include <SecureObjectSync/SOSCloudCircleInternal.h>
#include <SecureObjectSync/SOSInternal.h>
#include <SecureObjectSync/SOSEngine.h>
#include <SecureObjectSync/SOSPeer.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecFramework.h>
#include <Security/SecKey.h>
#include <Security/SecKeyPriv.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/der_plist.h>
#include <utilities/der_plist_internal.h>
#include <corecrypto/ccder.h>
#include <corecrypto/ccdigest.h>
#include <corecrypto/ccsha2.h>
#include <stdlib.h>
#include <assert.h>
enum {
kOnlyCompatibleVersion = 1,
kAlwaysIncompatibleVersion = UINT64_MAX,
};
struct __OpaqueSOSCircle {
CFRuntimeBase _base;
CFStringRef name;
CFNumberRef generation;
CFMutableArrayRef peers;
CFMutableArrayRef applicants;
CFMutableArrayRef rejected_applicants;
CFMutableDictionaryRef signatures;
};
CFGiblisWithCompareFor(SOSCircle);
static void SOSPeerRelease(CFAllocatorRef allocator, const void *value) {
SOSPeerDispose((SOSPeerRef)value);
}
static const CFDictionaryValueCallBacks dispose_peer_callbacks = { .release = SOSPeerRelease };
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 = CFArrayCreateMutableForCFTypes(allocator);
c->applicants = CFArrayCreateMutableForCFTypes(allocator);
c->rejected_applicants = CFArrayCreateMutableForCFTypes(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);
}
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 = CFArrayCreateMutableCopy(allocator, 0, otherCircle->peers);
c->applicants = CFArrayCreateMutableCopy(allocator, 0, otherCircle->applicants);
c->rejected_applicants = CFArrayCreateMutableCopy(allocator, 0, 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);
return NULL != left && NULL != right
&& CFEqual(left->generation, right->generation)
&& CFEqual(left->peers, right->peers)
&& CFEqual(left->applicants, right->applicants)
&& CFEqual(left->rejected_applicants, right->rejected_applicants)
&& CFEqual(left->signatures, right->signatures);
}
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 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);
SOSCircleDigestArray(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; 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) {
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) {
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 CFIndex CFArrayRemoveAllPassing(CFMutableArrayRef array, bool (^test)(const void *) ){
CFIndex numberRemoved = 0;
CFIndex position = 0;
while (position < CFArrayGetCount(array) && !test(CFArrayGetValueAtIndex(array, position)))
++position;
while (position < CFArrayGetCount(array)) {
CFArrayRemoveValueAtIndex(array, position);
++numberRemoved;
while (position < CFArrayGetCount(array) && !test(CFArrayGetValueAtIndex(array, position)))
++position;
}
return numberRemoved;
}
static CFIndex CFArrayRemoveAllWithMatchingID(CFMutableArrayRef array, SOSPeerInfoRef peerInfo) {
CFStringRef peer_id = SOSPeerInfoGetPeerID(peerInfo);
return CFArrayRemoveAllPassing(array, ^ bool (const void *element) {
SOSPeerInfoRef peer = (SOSPeerInfoRef) element;
return CFEqual(peer_id, SOSPeerInfoGetPeerID(peer));
});
}
static void SOSCircleRejectNonValidApplicants(SOSCircleRef circle, SecKeyRef pubkey) {
CFArrayRef applicants = SOSCircleCopyApplicants(circle, NULL);
CFArrayForEach(applicants, ^(const void *value) {
SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
if(!SOSPeerInfoApplicationVerify(pi, pubkey, NULL)) {
CFArrayRemoveAllWithMatchingID(circle->applicants, pi);
CFArrayAppendValue(circle->rejected_applicants, pi);
}
});
}
bool SOSCircleGenerationSign(SOSCircleRef circle, SecKeyRef user_approver, SOSFullPeerInfoRef peerinfo, CFErrorRef *error) {
SecKeyRef ourKey = SOSFullPeerInfoCopyDeviceKey(peerinfo, error);
require_quiet(ourKey, fail);
SOSCircleRemoveRetired(circle, error); CFArrayRemoveAllValues(circle->rejected_applicants); SOSCircleRejectNonValidApplicants(circle, SecKeyCreatePublicFromPrivate(user_approver));
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);
return true;
fail:
CFReleaseNull(ourKey);
return false;
}
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, CFErrorRef *error) {
SOSConcordanceStatus result = kSOSConcordanceNoPeer;
SecKeyRef pubKey = SOSPeerInfoCopyPubKey(peer);
require_action_quiet(SOSCircleHasActivePeer(circle, peer, 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;
}
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;
SOSCircleForEachActiveValidPeer(signers_circle, user_pubKey, ^(SOSPeerInfoRef peer) {
SOSConcordanceStatus peerStatus = CheckPeerStatus(status_circle, peer, error);
if (peerStatus == kSOSConcordanceNoPeerSig &&
(CFEqualSafe(SOSPeerInfoGetPeerID(peer), excluded_id) || SOSPeerInfoIsCloudIdentity(peer)))
peerStatus = kSOSConcordanceNoPeer;
status = CombineStatus(status, peerStatus); });
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; }
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));
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 = SOSPeerInfoArrayCreateFromDER(allocator, error, der_p, sequence_end);
cir->applicants = SOSPeerInfoArrayCreateFromDER(allocator, error, der_p, sequence_end);
cir->rejected_applicants = SOSPeerInfoArrayCreateFromDER(allocator, 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, SOSPeerInfoArrayGetDEREncodedSize(cir->peers, error)), fail);
require_quiet(accumulate_size(&total_payload, SOSPeerInfoArrayGetDEREncodedSize(cir->applicants, error)), fail);
require_quiet(accumulate_size(&total_payload, SOSPeerInfoArrayGetDEREncodedSize(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,
SOSPeerInfoArrayEncodeToDER(cir->peers, error, der,
SOSPeerInfoArrayEncodeToDER(cir->applicants, error, der,
SOSPeerInfoArrayEncodeToDER(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);
return CFStringCreateWithFormat(NULL, NULL,
CFSTR("<SOSCircle@%p: [ \nName: %@, \nPeers: %@,\nApplicants: %@,\nRejects: %@,\nSignatures: %@\n ] >"),
c, c->name, c->peers, c->applicants, c->rejected_applicants, c->signatures);
}
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 SOSCircleGenerationIncrement(SOSCircleRef circle) {
CFAllocatorRef allocator = CFGetAllocator(circle->generation);
int64_t value = SOSCircleGetGenerationSint(circle);
value++;
circle->generation = CFNumberCreate(allocator, kCFNumberSInt64Type, &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)CFArrayGetCount(circle->applicants);
}
bool SOSCircleHasApplicant(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) {
SOSCircleAssertStable(circle);
return CFArrayHasValueMatching(circle->applicants, ^bool(const void *value) {
return SOSPeerInfoCompareByID(value, peerInfo, NULL) == 0;
});
}
CFMutableArrayRef SOSCircleCopyApplicants(SOSCircleRef circle, CFAllocatorRef allocator) {
SOSCircleAssertStable(circle);
return CFArrayCreateMutableCopy(allocator, 0, circle->applicants);
}
int SOSCircleCountRejectedApplicants(SOSCircleRef circle) {
SOSCircleAssertStable(circle);
return (int)CFArrayGetCount(circle->rejected_applicants);
}
bool SOSCircleHasRejectedApplicant(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) {
SOSCircleAssertStable(circle);
return CFArrayHasValueMatching(circle->rejected_applicants, ^bool(const void *value) {
return SOSPeerInfoCompareByID(value, peerInfo, NULL) == 0;
});
}
SOSPeerInfoRef SOSCircleCopyRejectedApplicant(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) {
SOSCircleAssertStable(circle);
return (SOSPeerInfoRef) CFArrayGetValueMatching(circle->rejected_applicants, ^bool(const void *value) {
return SOSPeerInfoCompareByID(value, peerInfo, NULL) == 0;
});
}
CFMutableArrayRef SOSCircleCopyRejectedApplicants(SOSCircleRef circle, CFAllocatorRef allocator) {
SOSCircleAssertStable(circle);
return CFArrayCreateMutableCopy(allocator, 0, 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;
}
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 SOSCircleResetToEmpty(SOSCircleRef circle, CFErrorRef *error) {
CFArrayRemoveAllValues(circle->applicants);
CFArrayRemoveAllValues(circle->peers);
CFDictionaryRemoveAllValues(circle->signatures);
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);
}
CFIndex SOSCircleRemoveRetired(SOSCircleRef circle, CFErrorRef *error) {
CFIndex n = CFArrayRemoveAllPassing(circle->peers, ^ bool (const void *element) {
SOSPeerInfoRef peer = (SOSPeerInfoRef) element;
return SOSPeerInfoIsRetirementTicket(peer);
});
return n;
}
static bool SOSCircleRecordAdmission(SOSCircleRef circle, SecKeyRef user_pubkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) {
SOSCircleAssertStable(circle);
bool isPeer = SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(requestor), error);
require_action_quiet(!isPeer, fail, SOSCreateError(kSOSErrorAlreadyPeer, CFSTR("Cannot request admission when already a peer"), NULL, error));
CFIndex total = CFArrayRemoveAllWithMatchingID(circle->applicants, SOSFullPeerInfoGetPeerInfo(requestor));
(void) total; assert(total <= 1);
total = CFArrayRemoveAllWithMatchingID(circle->rejected_applicants, SOSFullPeerInfoGetPeerInfo(requestor));
(void) total; assert(total <= 1);
CFArrayAppendValue(circle->applicants, SOSFullPeerInfoGetPeerInfo(requestor));
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 = SOSCircleRecordAdmission(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 = SOSCircleRecordAdmission(circle, user_pubkey, requestor, error);
fail:
CFReleaseNull(user_pubkey);
return success;
}
bool SOSCircleUpdatePeerInfo(SOSCircleRef circle, SOSPeerInfoRef replacement_peer_info) {
__block bool replaced = false;
CFStringRef replacement_peer_id = SOSPeerInfoGetPeerID(replacement_peer_info);
CFMutableArrayModifyValues(circle->peers, ^const void *(const void *value) {
if (CFEqual(replacement_peer_id, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value))
&& !CFEqual(replacement_peer_info, value)) {
replaced = true;
return replacement_peer_info;
}
return value;
});
return replaced;
}
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;
}
CFArrayRemoveAllWithMatchingID(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);
CFIndex total = CFArrayRemoveAllWithMatchingID(circle->applicants, peerInfo);
SecKeyRef publicKey = NULL;
bool result = false;
require_action_quiet(total != 0, fail,
SOSCreateError(kSOSErrorNotApplicant, CFSTR("Cannot accept non-applicant"), NULL, error));
publicKey = SecKeyCreatePublicFromPrivate(user_privkey);
require_quiet(SOSPeerInfoApplicationVerify(peerInfo, publicKey, error), fail);
assert(total == 1);
CFArrayAppendValue(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);
#ifndef NDEBUG
CFIndex total =
#endif
CFArrayRemoveAllWithMatchingID(circle->applicants, peerInfo);
assert(total <= 1);
return true;
}
bool SOSCircleRemoveRejectedPeer(SOSCircleRef circle, SOSPeerInfoRef peerInfo, CFErrorRef *error) {
SOSCircleAssertStable(circle);
#ifndef NDEBUG
CFIndex total =
#endif
CFArrayRemoveAllWithMatchingID(circle->rejected_applicants, peerInfo);
assert(total <= 1);
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);
CFIndex total = CFArrayRemoveAllWithMatchingID(circle->applicants, peerInfo);
if (total == 0) {
SOSCreateError(kSOSErrorNotApplicant, CFSTR("Cannot reject non-applicant"), NULL, error);
return false;
}
assert(total == 1);
CFArrayAppendValue(circle->rejected_applicants, peerInfo);
return true;
}
bool SOSCircleAcceptRequests(SOSCircleRef circle, SecKeyRef user_privkey, SOSFullPeerInfoRef device_approver,
CFErrorRef *error) {
__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) {
__block bool result = false;
SecKeyRef userPubKey = SecKeyCreatePublicFromPrivate(userPrivKey);
SOSCircleForEachApplicant(circle, ^(SOSPeerInfoRef peer) {
if(!SOSPeerInfoApplicationVerify(peer, userPubKey, NULL)) {
if(!SOSCircleRejectRequest(circle, fpi, peer, NULL)) {
}
}
});
result = SOSCircleUpdatePeerInfo(circle, SOSFullPeerInfoGetPeerInfo(fpi));
if (result) {
SOSCircleGenerationSign(circle, userPrivKey, fpi, error);
secnotice("circle", "Generation signed updated signatures on peerinfo");
}
return result;
}
SOSPeerInfoRef SOSCircleCopyPeerInfo(SOSCircleRef circle, CFStringRef peer_id, CFErrorRef *error) {
__block SOSPeerInfoRef result = NULL;
CFArrayForEach(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 inline void SOSCircleForEachPeerMatching(SOSCircleRef circle,
void (^action)(SOSPeerInfoRef peer),
bool (^condition)(SOSPeerInfoRef peer)) {
CFArrayForEach(circle->peers, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
if (condition(peer))
action(peer);
});
}
void SOSCircleForEachPeer(SOSCircleRef circle, void (^action)(SOSPeerInfoRef peer)) {
SOSCircleForEachPeerMatching(circle, action, ^bool(SOSPeerInfoRef peer) {
return !SOSPeerInfoIsRetirementTicket(peer) && !SOSPeerInfoIsCloudIdentity(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)) {
CFArrayForEach(circle->applicants, ^(const void*value) { action((SOSPeerInfoRef) value); } );
}
CFMutableArrayRef SOSCircleCopyPeers(SOSCircleRef circle, CFAllocatorRef allocator) {
SOSCircleAssertStable(circle);
CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
CFArrayAppendValue(result, peer);
});
return result;
}
CFMutableArrayRef SOSCircleCopyConcurringPeers(SOSCircleRef circle, CFErrorRef* error) {
SOSCircleAssertStable(circle);
CFMutableArrayRef concurringPeers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
CFErrorRef error = NULL;
if (SOSCircleVerifyPeerSigned(circle, peer, &error)) {
CFArrayAppendValue(concurringPeers, peer);
} else if (error != NULL) {
secerror("Error checking concurrence: %@", error);
}
CFReleaseNull(error);
});
return concurringPeers;
}
static SOSPeerRef SOSCircleCopyPeer(SOSCircleRef circle, SOSFullPeerInfoRef myRef, SOSPeerSendBlock sendBlock,
CFStringRef peer_id, CFErrorRef *error) {
SOSPeerRef peer = NULL;
SOSPeerInfoRef peer_info = SOSCircleCopyPeerInfo(circle, peer_id, error);
if (peer_info) {
peer = SOSPeerCreate(myRef, peer_info, error, sendBlock);
CFReleaseNull(peer_info);
}
return peer;
}
static bool SOSCircleDoWithPeer(SOSFullPeerInfoRef myRef, SOSCircleRef circle, SOSDataSourceFactoryRef factory,
SOSPeerSendBlock sendBlock, CFStringRef peer_id, bool readOnly,
CFErrorRef* error, bool (^do_action)(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error))
{
bool success = false;
SOSEngineRef engine = NULL;
SOSPeerRef peer = NULL;
SOSDataSourceRef ds = NULL;
peer = SOSCircleCopyPeer(circle, myRef, sendBlock, peer_id, error);
require(peer, exit);
ds = factory->create_datasource(factory, SOSCircleGetName(circle), readOnly, error);
require(ds, exit);
engine = SOSEngineCreate(ds, error); ds = NULL;
require(engine, exit);
success = do_action(engine, peer, error);
exit:
if (ds)
ds->release(ds);
if (engine)
SOSEngineDispose(engine);
if (peer)
SOSPeerDispose(peer);
return success;
}
bool SOSCircleSyncWithPeer(SOSFullPeerInfoRef myRef, SOSCircleRef circle, SOSDataSourceFactoryRef factory,
SOSPeerSendBlock sendBlock, CFStringRef peer_id,
CFErrorRef *error)
{
return SOSCircleDoWithPeer(myRef, circle, factory, sendBlock, peer_id, true, error, ^bool(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) {
return SOSPeerStartSync(peer, engine, error) != kSOSPeerCoderFailure;
});
}
bool SOSCircleHandlePeerMessage(SOSCircleRef circle, SOSFullPeerInfoRef myRef, SOSDataSourceFactoryRef factory,
SOSPeerSendBlock sendBlock, CFStringRef peer_id,
CFDataRef message, CFErrorRef *error) {
return SOSCircleDoWithPeer(myRef, circle, factory, sendBlock, peer_id, false, error, ^bool(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) {
return SOSPeerHandleMessage(peer, engine, message, error) != kSOSPeerCoderFailure;
});
}
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;
}