SOSPeerInfo.c   [plain text]


//
//  SOSPeerInfo.c
//  sec
//
//  Created by Mitch Adler on 7/19/12.
//
//

#include <AssertMacros.h>
#include <TargetConditionals.h>

#include <SecureObjectSync/SOSPeerInfo.h>
#include <SecureObjectSync/SOSPeerInfoInternal.h>
#include <SecureObjectSync/SOSCircle.h>

#include <SecureObjectSync/SOSInternal.h>
#include <ipc/securityd_client.h>

#include "Imported/SecuritydXPC.h"

#include <CoreFoundation/CFArray.h>
#include <dispatch/dispatch.h>

#include <stdlib.h>
#include <assert.h>

#include <utilities/SecCFWrappers.h>
#include <utilities/SecCFRelease.h>
#include <utilities/SecCFError.h>
#include <utilities/SecXPCError.h>

#include <utilities/der_plist.h>
#include <utilities/der_plist_internal.h>
#include <corecrypto/ccder.h>
#include <utilities/der_date.h>

#include <corecrypto/ccdigest.h>
#include <corecrypto/ccsha2.h>


#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFDate.h>

#include <xpc/xpc.h>

#if TARGET_OS_IPHONE || TARGET_OS_EMBEDDED
#include <MobileGestalt.h>
#endif

#include <Security/SecBase64.h>
#include <Security/SecKeyPriv.h>
#include <Security/SecOTR.h>

#if 0//TARGET_OS_MAC // TODO: this function is the only one that causes secd to need to link against Security.framework on OSX

__BEGIN_DECLS
SecKeyRef _SecKeyCreateFromPublicData(CFAllocatorRef allocator, CFIndex algorithmID, CFDataRef publicBytes);
__END_DECLS

#endif  /* TARGET_OS_MAC */

struct __OpaqueSOSPeerInfo {
    CFRuntimeBase          _base;
    
    //
    CFMutableDictionaryRef description;
    CFDataRef              signature;
    
    // Cached data
    CFDictionaryRef        gestalt;
    CFStringRef            id;
    CFIndex                version;
};

CFGiblisWithHashFor(SOSPeerInfo);

CFStringRef kPIUserDefinedDeviceName = CFSTR("ComputerName");
CFStringRef kPIDeviceModelName = CFSTR("ModelName");

// Description Dictionary Entries
static CFStringRef sPublicKeyKey = CFSTR("PublicSigningKey");
static CFStringRef sGestaltKey   = CFSTR("DeviceGestalt");
static CFStringRef sVersionKey   = CFSTR("ConflictVersion");
static CFStringRef sCloudIdentityKey   = CFSTR("CloudIdentity");
static CFStringRef sApplicationDate   = CFSTR("ApplicationDate");
static CFStringRef sApplicationUsig   = CFSTR("ApplicationUsig");
static CFStringRef sRetirementDate   = CFSTR("RetirementDate");

// Peerinfo Entries
CFStringRef kSOSPeerInfoDescriptionKey = CFSTR("SOSPeerInfoDescription");
CFStringRef kSOSPeerInfoSignatureKey = CFSTR("SOSPeerInfoSignature");
CFStringRef kSOSPeerInfoNameKey = CFSTR("SOSPeerInfoName");


SecKeyRef SOSPeerInfoCopyPubKey(SOSPeerInfoRef peer) {
    CFDataRef pubKeyBytes = CFDictionaryGetValue(peer->description, sPublicKeyKey);
    CFAllocatorRef allocator = CFGetAllocator(peer);
    SecKeyRef pubKey = SecKeyCreateFromPublicData(allocator, kSecECDSAAlgorithmID, pubKeyBytes);
    return pubKey;
}


static bool SOSDescriptionHash(SOSPeerInfoRef peer, const struct ccdigest_info *di, void *hashresult, CFErrorRef *error) {
    ccdigest_di_decl(di, ctx);
    ccdigest_init(di, ctx);
    void *ctx_p = ctx;
    if(!SOSPeerInfoUpdateDigestWithDescription(peer, di, ctx_p, error)) return false;
    ccdigest_final(di, ctx, hashresult);
    return true;
}


#define SIGLEN 128
static CFDataRef sosSignHash(SecKeyRef privkey, const struct ccdigest_info *di, uint8_t *hbuf) {
    OSStatus stat;
    size_t siglen = SIGLEN;
    uint8_t sig[siglen];
    if((stat = SecKeyRawSign(privkey, kSecPaddingNone, hbuf, di->output_size, sig, &siglen)) != 0) {
        return NULL;
    }
    return CFDataCreate(NULL, sig, (CFIndex)siglen);
}

static bool sosVerifyHash(SecKeyRef pubkey, const struct ccdigest_info *di, uint8_t *hbuf, CFDataRef signature) {
    return SecKeyRawVerify(pubkey, kSecPaddingNone, hbuf, di->output_size,
                           CFDataGetBytePtr(signature), CFDataGetLength(signature)) == errSecSuccess;
}

static bool SOSPeerInfoSign(SecKeyRef privKey, SOSPeerInfoRef peer, CFErrorRef *error) {
    bool status = false;
    const struct ccdigest_info *di = ccsha256_di();
    uint8_t hbuf[di->output_size];
    CFDataRef newSignature = NULL;
    
    require_action_quiet(SOSDescriptionHash(peer, di, hbuf, error), fail,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Failed to hash description for peer"), NULL, error));
    
    newSignature = sosSignHash(privKey, di, hbuf);
    require_action_quiet(newSignature, fail, SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Failed to sign peerinfo for peer"), NULL, error));

    CFReleaseNull(peer->signature);
    peer->signature = newSignature;
    newSignature = NULL;
    status = true;

fail:
    CFReleaseNull(newSignature);
    return status;
}

// Return true (1) if the signature verifies.
static bool SOSPeerInfoVerify(SOSPeerInfoRef peer, CFErrorRef *error) {
    bool result = false;
    const struct ccdigest_info *di = ccsha256_di();
    uint8_t hbuf[di->output_size];

    SecKeyRef pubKey = SOSPeerInfoCopyPubKey(peer);
    require_quiet(pubKey, error_out);

    require_quiet(SOSDescriptionHash(peer, di, hbuf, error), error_out);
    
    result = sosVerifyHash(pubKey, di, hbuf, peer->signature);

error_out:
    CFReleaseNull(pubKey);
    return result;
}

static SOSPeerInfoRef SOSPeerInfoCreate_Internal(CFAllocatorRef allocator, CFDictionaryRef gestalt, SecKeyRef signingKey, CFErrorRef* error, void (^ description_modifier)(CFMutableDictionaryRef description)) {
    SOSPeerInfoRef pi = CFTypeAllocate(SOSPeerInfo, struct __OpaqueSOSPeerInfo, allocator);
    pi->gestalt = gestalt;
    CFRetain(pi->gestalt);
    
    pi->version = kSOSPeerVersion;

    CFDataRef publicBytes = NULL;
    CFNumberRef versionNumber = NULL;

    SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(signingKey);
    if (publicKey == NULL) {
        SOSCreateError(kSOSErrorBadKey, CFSTR("Unable to get public"), NULL, error);
        CFReleaseNull(pi);
        goto exit;
    }

    OSStatus result = SecKeyCopyPublicBytes(publicKey, &publicBytes);
    
    if (result != errSecSuccess) {
        SOSCreateError(kSOSErrorBadKey, CFSTR("Failed to export public bytes"), NULL, error);
        CFReleaseNull(pi);
        goto exit;
    }
    
    pi->signature = CFDataCreateMutable(allocator, 0);
    
    versionNumber = CFNumberCreateWithCFIndex(NULL, pi->version);
    pi->description = CFDictionaryCreateMutableForCFTypesWith(allocator,
                                                              sVersionKey,   versionNumber,
                                                              sPublicKeyKey, publicBytes,
                                                              sGestaltKey,   pi->gestalt,
                                                              NULL);
    description_modifier(pi->description);
    
    pi->id = SOSCopyIDOfKey(publicKey, error);
    CFReleaseNull(publicKey);

    require_quiet(pi->id, exit);

    if (!SOSPeerInfoSign(signingKey, pi, error)) {
        CFReleaseNull(pi);
        goto exit;
    }

exit:
    CFReleaseNull(versionNumber);
    CFReleaseNull(publicBytes);
    return pi;
}

SOSPeerInfoRef SOSPeerInfoCreate(CFAllocatorRef allocator, CFDictionaryRef gestalt, SecKeyRef signingKey, CFErrorRef* error) {
    return SOSPeerInfoCreate_Internal(allocator, gestalt, signingKey, error, ^(CFMutableDictionaryRef description) {});
}

SOSPeerInfoRef SOSPeerInfoCreateCloudIdentity(CFAllocatorRef allocator, CFDictionaryRef gestalt, SecKeyRef signingKey, CFErrorRef* error) {
    return SOSPeerInfoCreate_Internal(allocator, gestalt, signingKey, error, ^(CFMutableDictionaryRef description) {
        CFDictionarySetValue(description, sCloudIdentityKey, kCFBooleanTrue);
    });

}


SOSPeerInfoRef SOSPeerInfoCreateCopy(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFErrorRef* error) {
    SOSPeerInfoRef pi = CFTypeAllocate(SOSPeerInfo, struct __OpaqueSOSPeerInfo, allocator);
    
    pi->description = CFDictionaryCreateMutableCopy(allocator, 0, toCopy->description);
    pi->signature = CFDataCreateCopy(allocator, toCopy->signature);
    
    pi->gestalt = CFDictionaryCreateCopy(allocator, toCopy->gestalt);
    pi->id = CFStringCreateCopy(allocator, toCopy->id);

    pi->version = toCopy->version;

    return pi;
}

SOSPeerInfoRef SOSPeerInfoCopyWithGestaltUpdate(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFDictionaryRef gestalt, SecKeyRef signingKey, CFErrorRef* error) {
    SOSPeerInfoRef pi = SOSPeerInfoCreateCopy(allocator, toCopy, error);

    CFRetainSafe(gestalt);
    CFReleaseNull(pi->gestalt);
    pi->gestalt = gestalt;

    CFDictionarySetValue(pi->description, sGestaltKey, pi->gestalt);

    SecKeyRef pub_key = SOSPeerInfoCopyPubKey(pi);
   
    pi->id = SOSCopyIDOfKey(pub_key, error);
    require_quiet(pi->id, exit);

    require_action_quiet(SOSPeerInfoSign(signingKey, pi, error), exit, CFReleaseNull(pi));

exit:
    CFReleaseNull(pub_key);
    return pi;
}

SOSPeerInfoRef SOSPeerInfoCreateFromDER(CFAllocatorRef allocator, CFErrorRef* error,
                                        const uint8_t** der_p, const uint8_t *der_end) {
    SOSPeerInfoRef pi = CFTypeAllocate(SOSPeerInfo, struct __OpaqueSOSPeerInfo, allocator);
    SecKeyRef pubKey = NULL;

    const uint8_t *sequence_end;

    CFPropertyListRef pl = NULL;
    
    pi->gestalt = NULL;
    pi->version = 0; // TODO: Encode this in the DER
    
    *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
    *der_p = der_decode_plist(allocator, kCFPropertyListImmutable, &pl, error, *der_p, sequence_end);
    *der_p = der_decode_data(allocator, kCFPropertyListImmutable, &pi->signature, error, *der_p, sequence_end);
        
    if (*der_p == NULL || *der_p != sequence_end) {
        SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Format of Peer Info DER"), NULL, error);
        goto fail;
    }

    if (CFGetTypeID(pl) != CFDictionaryGetTypeID()) {
        CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(pl));
        SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL,
                                 CFSTR("Expected dictionary got %@"), description);
        CFReleaseSafe(description);
        goto fail;
    }
    
    pi->description = (CFMutableDictionaryRef) pl;
    CFRetain(pi->description);
    CFReleaseNull(pl);
    
    CFNumberRef versionNumber = CFDictionaryGetValue(pi->description, sVersionKey);

    if (versionNumber) {
        CFNumberGetValue(versionNumber, kCFNumberCFIndexType, &pi->version);
    }

    CFDictionaryRef gestalt = CFDictionaryGetValue(pi->description, sGestaltKey);

    if (!isDictionary(gestalt)) {
        CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(pl));
        SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL,
                                 CFSTR("Expected dictionary got %@"), description);
        CFReleaseSafe(description);
        goto fail;
    }

    pi->gestalt = gestalt;
    CFRetain(pi->gestalt);

    pubKey = SOSPeerInfoCopyPubKey(pi);
    require_quiet(pubKey, fail);

    pi->id = SOSCopyIDOfKey(pubKey, error);
    require_quiet(pi->id, fail);

    if(!SOSPeerInfoVerify(pi, error)) {
        SOSCreateErrorWithFormat(kSOSErrorBadSignature, NULL, error, NULL, CFSTR("Signature doesn't validate"));
        if (error)
            secerror("Can't validate PeerInfo: %@", *error);
        goto fail;
    }
    CFReleaseNull(pubKey);
    return pi;

fail:
    CFReleaseNull(pi);
    CFReleaseNull(pl);
    CFReleaseNull(pubKey);

    return NULL;
}

SOSPeerInfoRef SOSPeerInfoCreateFromData(CFAllocatorRef allocator, CFErrorRef* error,
                                        CFDataRef peerinfo_data) {
    const uint8_t *der = CFDataGetBytePtr(peerinfo_data);
    CFIndex len = CFDataGetLength(peerinfo_data);
    return SOSPeerInfoCreateFromDER(NULL, error, &der, der+len);
}

static void SOSPeerInfoDestroy(CFTypeRef aObj) {
    SOSPeerInfoRef pi = (SOSPeerInfoRef) aObj;
    
    if(!pi) return;
    CFReleaseNull(pi->description);
    CFReleaseNull(pi->signature);
    CFReleaseNull(pi->gestalt);
    CFReleaseNull(pi->id);
}

static Boolean SOSPeerInfoCompare(CFTypeRef lhs, CFTypeRef rhs) {
    SOSPeerInfoRef lpeer = (SOSPeerInfoRef) lhs;
    SOSPeerInfoRef rpeer = (SOSPeerInfoRef) rhs;
    if(!lpeer || !rpeer) return false;
    return CFEqualSafe(lpeer->description, rpeer->description) && CFEqualSafe(lpeer->signature, rpeer->signature);
}


CFComparisonResult SOSPeerInfoCompareByID(const void *val1, const void *val2, void *context) {
	CFStringRef v1 = SOSPeerInfoGetPeerID((SOSPeerInfoRef) val1);
	CFStringRef v2 = SOSPeerInfoGetPeerID((SOSPeerInfoRef) val2);
    return CFStringCompare(v1, v2, 0);
}

static CFHashCode SOSPeerInfoHash(CFTypeRef cf) {
    SOSPeerInfoRef peer = (SOSPeerInfoRef) cf;

    return CFHash(peer->description) ^ CFHash(peer->signature);
}

static CFStringRef SOSPeerInfoCopyDescription(CFTypeRef aObj) {
    SOSPeerInfoRef pi = (SOSPeerInfoRef) aObj;

    return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSPeerInfo@%p: Name:'%@'%s Type: '%@' ID:'%@'>"),
                                    pi,
                                    CFDictionaryGetValue(pi->gestalt, kPIUserDefinedDeviceName),
                                    SOSPeerInfoIsRetirementTicket(pi) ? " [retired]" : "",
                                    CFDictionaryGetValue(pi->gestalt, kPIDeviceModelName),
                                    pi->id);
}

CFDictionaryRef SOSPeerInfoCopyPeerGestalt(SOSPeerInfoRef pi) {
    CFRetain(pi->gestalt);
    return pi->gestalt;
}

CFStringRef SOSPeerInfoGetPeerName(SOSPeerInfoRef peer) {
    return SOSPeerInfoLookupGestaltValue(peer, kPIUserDefinedDeviceName);
}

CFStringRef SOSPeerInfoGetPeerDeviceType(SOSPeerInfoRef peer) {
    return SOSPeerInfoLookupGestaltValue(peer, kPIDeviceModelName);
}

CFTypeRef SOSPeerInfoLookupGestaltValue(SOSPeerInfoRef pi, CFStringRef key) {
    return CFDictionaryGetValue(pi->gestalt, key);
}

CFStringRef SOSPeerInfoGetPeerID(SOSPeerInfoRef pi) {
    return pi->id;
}

CFIndex SOSPeerInfoGetVersion(SOSPeerInfoRef pi) {
    // TODO: Encode this in the DER.
    return pi->version;
}

bool SOSPeerInfoUpdateDigestWithPublicKeyBytes(SOSPeerInfoRef peer, const struct ccdigest_info *di,
                                               ccdigest_ctx_t ctx, CFErrorRef *error) {
    CFDataRef pubKeyBytes = CFDictionaryGetValue(peer->description, sPublicKeyKey);
    
    if(!pubKeyBytes) {
        SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, NULL, error, NULL, CFSTR("Digest failed – no public key"));
        return false;
    }
    
    ccdigest_update(di, ctx, CFDataGetLength(pubKeyBytes), CFDataGetBytePtr(pubKeyBytes));
    
    return true;
}

bool SOSPeerInfoUpdateDigestWithDescription(SOSPeerInfoRef peer, const struct ccdigest_info *di,
                                            ccdigest_ctx_t ctx, CFErrorRef *error) {
    size_t description_size = der_sizeof_plist(peer->description, error);
    uint8_t data_begin[description_size];
    uint8_t *data_end = data_begin + description_size;
    uint8_t *encoded = der_encode_plist(peer->description, error, data_begin, data_end);
    
    if(!encoded) {
        SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, NULL, error, NULL, CFSTR("Description encode failed"));
        return false;
    }
    
    ccdigest_update(di, ctx, description_size, data_begin);
    
    return true;
}


static CFDataRef sosCreateDate() {
    CFDateRef now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
    size_t bufsiz = der_sizeof_date(now, NULL);
    uint8_t buf[bufsiz];
    der_encode_date(now, NULL, buf, buf+bufsiz);
    CFReleaseNull(now);
    return CFDataCreate(NULL, buf, bufsiz);
}

static CFDateRef sosCreateCFDate(CFDataRef sosdate) {
    CFDateRef date;
    der_decode_date(NULL, 0, &date, NULL, CFDataGetBytePtr(sosdate),
                    CFDataGetBytePtr(sosdate) + CFDataGetLength(sosdate));
    return date;
}

static bool sospeer_application_hash(SOSPeerInfoRef pi, const struct ccdigest_info *di, uint8_t *hbuf) {
    CFDataRef appdate = CFDictionaryGetValue(pi->description, sApplicationDate);
    if(!appdate) return false;
    ccdigest_di_decl(di, ctx);
    ccdigest_init(di, ctx);
    ccdigest_update(di, ctx, CFDataGetLength(appdate), CFDataGetBytePtr(appdate));
    if (!SOSPeerInfoUpdateDigestWithPublicKeyBytes(pi, di, ctx, NULL)) return false;
    ccdigest_final(di, ctx, hbuf);
    return true;
}

SOSPeerInfoRef SOSPeerInfoCopyAsApplication(SOSPeerInfoRef original, SecKeyRef userkey, SecKeyRef peerkey, CFErrorRef *error) {
    SOSPeerInfoRef result = NULL;
    SOSPeerInfoRef pi = SOSPeerInfoCreateCopy(kCFAllocatorDefault, original, error);

    const struct ccdigest_info *di = ccsha256_di();
    uint8_t hbuf[di->output_size];
    CFDataRef usersig = NULL;
    
    CFDataRef creationDate = sosCreateDate();
    CFDictionarySetValue(pi->description, sApplicationDate, creationDate);
    CFReleaseNull(creationDate);

    // Create User Application Signature
    require_action_quiet(sospeer_application_hash(pi, di, hbuf), fail,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Failed to create hash for peer applicant"), NULL, error));
    
    usersig = sosSignHash(userkey, di, hbuf);
    require_action_quiet(usersig, fail,
                        SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Failed to sign public key hash for peer"), NULL, error));

    CFDictionarySetValue(pi->description, sApplicationUsig, usersig);
    
    require_quiet(SOSPeerInfoSign(peerkey, pi, error), fail);

    result = pi;
    pi = NULL;

fail:
    CFReleaseNull(usersig);
    CFReleaseNull(pi);
    return result;
}

bool SOSPeerInfoApplicationVerify(SOSPeerInfoRef pi, SecKeyRef userkey, CFErrorRef *error) {
    const struct ccdigest_info *di = ccsha256_di();
    uint8_t hbuf[di->output_size];
    bool result = false;

    CFDataRef usig = CFDictionaryGetValue(pi->description, sApplicationUsig);
    require_action_quiet(usig, exit,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Peer is not an applicant"), NULL, error));
    // Verify User Application Signature
    require_action_quiet(sospeer_application_hash(pi, di, hbuf), exit,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Failed to create hash for peer applicant"), NULL, error));
    require_action_quiet(sosVerifyHash(userkey, di, hbuf, usig), exit,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("user signature of public key hash fails to verify"), NULL, error));

    result = SOSPeerInfoVerify(pi, error);

exit:
    return result;
}


static CF_RETURNS_RETAINED CFDateRef sosPeerInfoGetDate(SOSPeerInfoRef pi, CFStringRef entry) {
    if(!pi) return NULL;
    CFDataRef sosdate = CFDictionaryGetValue(pi->description, entry);
    if(!sosdate) return NULL;
    CFDateRef date = sosCreateCFDate(sosdate);
    
    return date;
}

CF_RETURNS_RETAINED CFDateRef SOSPeerInfoGetApplicationDate(SOSPeerInfoRef pi) {
    return sosPeerInfoGetDate(pi, sApplicationDate);
}

CF_RETURNS_RETAINED CFDateRef SOSPeerInfoGetRetirementDate(SOSPeerInfoRef pi) {
    return sosPeerInfoGetDate(pi, sRetirementDate);
}


size_t SOSPeerInfoGetDEREncodedSize(SOSPeerInfoRef peer, CFErrorRef *error) {
    size_t plist_size = der_sizeof_plist(peer->description, error);
    if (plist_size == 0)
        return 0;
    
    size_t signature_size = der_sizeof_data(peer->signature, error);
    if (signature_size == 0)
        return 0;
    
    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
                        plist_size + signature_size);
}

uint8_t* SOSPeerInfoEncodeToDER(SOSPeerInfoRef peer, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) {
    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
                                       der_encode_plist(peer->description, error, der,
                                       der_encode_data(peer->signature, error, der, der_end)));
}

CFDataRef SOSPeerInfoCopyEncodedData(SOSPeerInfoRef peer, CFAllocatorRef allocator, CFErrorRef *error) {
    size_t size = SOSPeerInfoGetDEREncodedSize(peer, error);
    if (size == 0) return NULL;

    uint8_t buffer[size];
    uint8_t* start = SOSPeerInfoEncodeToDER(peer, error, buffer, buffer + sizeof(buffer));
    CFDataRef result = CFDataCreate(kCFAllocatorDefault, start, size);
    return result;
}


//
// PeerInfoArray encoding decoding
//

CFMutableArrayRef SOSPeerInfoArrayCreateFromDER(CFAllocatorRef allocator, CFErrorRef* error,
                                                const uint8_t** der_p, const uint8_t *der_end) {
    CFMutableArrayRef pia = CFArrayCreateMutableForCFTypes(allocator);

    const uint8_t *sequence_end;

    *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);

    require_action(*der_p, fail, SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Peer Info Array Sequence Header"), NULL, error));

    while (sequence_end != *der_p) {
        SOSPeerInfoRef pi = SOSPeerInfoCreateFromDER(allocator, error, der_p, sequence_end);

        if (pi == NULL || *der_p == NULL) {
            SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Peer Info Array DER"), (error != NULL ? *error : NULL), error);
            CFReleaseNull(pi);
            goto fail;
        }

        CFArrayAppendValue(pia, pi);
        CFReleaseSafe(pi);
    }

    if (!pia)
        *der_p = NULL;
    return pia;

fail:
    CFReleaseNull(pia);
    *der_p = NULL;
    return NULL;
}

size_t SOSPeerInfoArrayGetDEREncodedSize(CFArrayRef pia, CFErrorRef *error) {
    size_t array_size = 0;

    for(CFIndex count = CFArrayGetCount(pia);
        count > 0;
        --count) {
        SOSPeerInfoRef pi = (SOSPeerInfoRef) CFArrayGetValueAtIndex(pia, count - 1);

        if (CFGetTypeID(pi) != SOSPeerInfoGetTypeID()) {
            SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Non SOSPeerInfo in array"), (error != NULL ? *error : NULL), error);
            return 0;
        }

        size_t pi_size = SOSPeerInfoGetDEREncodedSize(pi, error);

        if (pi_size == 0) {
            SOSCreateError(kSOSErrorEncodeFailure, CFSTR("Bad DER size"), (error != NULL ? *error : NULL), error);
            return 0;
        }

        array_size += pi_size;
    }


    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
                        array_size);
}

uint8_t* SOSPeerInfoArrayEncodeToDER(CFArrayRef pia, CFErrorRef* error, const uint8_t* der, uint8_t* der_end_param) {

    uint8_t* const sequence_end = der_end_param;
    __block uint8_t* der_end = der_end_param;

    CFArrayForEachReverse(pia, ^(const void *value) {
        SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
        if (CFGetTypeID(pi) != SOSPeerInfoGetTypeID()) {
            SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Non SOSPeerInfo in array"), NULL, error);
            der_end = NULL; // Indicate error and continue.
        }
        if (der_end)
            der_end = SOSPeerInfoEncodeToDER(pi, error, der, der_end);
    });
    
    if (der_end == NULL)
        return NULL;

    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, sequence_end, der, der_end);
}


CFArrayRef CreateArrayOfPeerInfoWithXPCObject(xpc_object_t peerArray, CFErrorRef* error) {
    if (!peerArray) {
        SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedNull, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("Unexpected Null Array to encode"));
        return NULL;
    }
    
    if (xpc_get_type(peerArray) != XPC_TYPE_DATA) {
        SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("Array of peer info not array, got %@"), peerArray);
        return NULL;
    }
    
    const uint8_t* der = xpc_data_get_bytes_ptr(peerArray);
    const uint8_t* der_end = der + xpc_data_get_length(peerArray);
    
    return SOSPeerInfoArrayCreateFromDER(kCFAllocatorDefault, error, &der, der_end);
}

xpc_object_t CreateXPCObjectWithArrayOfPeerInfo(CFArrayRef array, CFErrorRef *error) {
    size_t data_size = SOSPeerInfoArrayGetDEREncodedSize(array, error);
    if (data_size == 0)
        return NULL;
    uint8_t *data = (uint8_t *)malloc(data_size);
    if (!data) return NULL;
    
    xpc_object_t result = NULL;
    if (SOSPeerInfoArrayEncodeToDER(array, error, data, data + data_size))
        result = xpc_data_create(data, data_size);

    free(data);
    return result;
}

//
// Gestalt helpers
//

CFStringRef SOSPeerGestaltGetName(CFDictionaryRef gestalt) {
    CFStringRef name = SOSPeerGestaltGetAnswer(gestalt, kPIUserDefinedDeviceName);
    return isString(name) ? name : NULL;
}

CFTypeRef SOSPeerGestaltGetAnswer(CFDictionaryRef gestalt, CFStringRef question) {
    return gestalt ? CFDictionaryGetValue(gestalt, question) : NULL;
}

//
// Peer Retirement
//


SOSPeerInfoRef SOSPeerInfoCreateRetirementTicket(CFAllocatorRef allocator, SecKeyRef privKey, SOSPeerInfoRef peer, CFErrorRef *error) {
    // Copy PeerInfo
    SOSPeerInfoRef pi = SOSPeerInfoCreateCopy(allocator, peer, error);

    require(pi, fail);

    // Fill out Resignation Date
    CFDataRef resignationDate = sosCreateDate();
    CFDictionaryAddValue(pi->description, sRetirementDate, resignationDate);
    CFReleaseNull(resignationDate);

    require(SOSPeerInfoSign(privKey, pi, error), fail);

    return pi;

fail:
    CFReleaseNull(pi);
    return NULL;
}

CFStringRef SOSPeerInfoInspectRetirementTicket(SOSPeerInfoRef pi, CFErrorRef *error) {
    CFStringRef retval = NULL;
    CFDateRef now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
    CFDateRef retirement = NULL;
    
    require_quiet(SOSPeerInfoVerify(pi, error), err);

    retirement = sosCreateCFDate(CFDictionaryGetValue(pi->description, sRetirementDate));

    require_action_quiet(retirement, err,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Peer is not retired"), NULL, error));

    require_action_quiet(CFDateCompare(now, retirement, NULL) == kCFCompareGreaterThan, err,
                         SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Retirement date is after current date"), NULL, error));

    retval = SOSPeerInfoGetPeerID(pi);

err:
    CFReleaseNull(now);
    CFReleaseNull(retirement);
    return retval;
}

bool SOSPeerInfoRetireRetirementTicket(size_t max_seconds, SOSPeerInfoRef pi) {
    CFDateRef now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
    CFDateRef retirement = sosCreateCFDate(CFDictionaryGetValue(pi->description, sRetirementDate));
    CFTimeInterval timediff = CFDateGetTimeIntervalSinceDate(now, retirement); // diff in seconds
    CFReleaseNull(now);
    CFReleaseNull(retirement);
    if(timediff > (max_seconds)) return true;
    return false;
}

bool SOSPeerInfoIsRetirementTicket(SOSPeerInfoRef pi) {
    CFDataRef flag = CFDictionaryGetValue(pi->description, sRetirementDate);
    return flag != NULL;
}

bool SOSPeerInfoIsCloudIdentity(SOSPeerInfoRef pi) {
    CFTypeRef value = CFDictionaryGetValue(pi->description, sCloudIdentityKey);
    return CFEqualSafe(value, kCFBooleanTrue);
}

SOSPeerInfoRef SOSPeerInfoUpgradeSignatures(CFAllocatorRef allocator, SecKeyRef privKey, SecKeyRef peerKey, SOSPeerInfoRef peer, CFErrorRef *error) {
    SecKeyRef pubKey = SecKeyCreatePublicFromPrivate(privKey);
    SOSPeerInfoRef retval = NULL;
    
    retval = SOSPeerInfoCopyAsApplication(peer, privKey, peerKey, error);
    CFReleaseNull(pubKey);
    return retval;
}