X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/sec/SOSCircle/SecureObjectSync/SOSPeerInfo.c?ds=inline diff --git a/Security/sec/SOSCircle/SecureObjectSync/SOSPeerInfo.c b/Security/sec/SOSCircle/SecureObjectSync/SOSPeerInfo.c new file mode 100644 index 00000000..d4db2ae9 --- /dev/null +++ b/Security/sec/SOSCircle/SecureObjectSync/SOSPeerInfo.c @@ -0,0 +1,774 @@ +/* + * 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@ + */ + + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +#include +#include + +#include + +#if TARGET_OS_IPHONE || TARGET_OS_EMBEDDED +#include +#endif + +#include +#include +#include +#include + +#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; + + CFStringRef transportType; + CFStringRef deviceID; +}; + +CFGiblisWithHashFor(SOSPeerInfo); + +CFStringRef kPIUserDefinedDeviceName = CFSTR("ComputerName"); +CFStringRef kPIDeviceModelName = CFSTR("ModelName"); +CFStringRef kPIMessageProtocolVersion = CFSTR("MessageProtocolVersion"); + +// 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); + if (pubKeyBytes == NULL) + return NULL; + 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_action_quiet(pubKey, error_out, + SOSErrorCreate(kSOSErrorNoKey, error, NULL, + CFSTR("Couldn't find pub key for %@"), peer)); + + require_quiet(SOSDescriptionHash(peer, di, hbuf, error), error_out); + + require_action_quiet(sosVerifyHash(pubKey, di, hbuf, peer->signature), error_out, + SOSErrorCreate(kSOSErrorBadSignature, error, NULL, + CFSTR("Signature didn't verify for %@"), peer)); + result = true; + +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 = SOSPeerInfoGetPeerProtocolVersion(pi); + pi->transportType = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("KVS")); + 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); + + CFStringRef deviceName = CFDictionaryGetValue(pi->gestalt, kPIUserDefinedDeviceName); + CFStringRef modelName = CFDictionaryGetValue(pi->gestalt, kPIDeviceModelName); + CFStringRef ID = pi->id; + CFMutableStringRef description = CFStringCreateMutableCopy(kCFAllocatorDefault, CFStringGetLength(deviceName), deviceName); + if(modelName){ + CFStringAppend(description, CFSTR(", ")); + CFStringAppend(description, modelName); + } + if(ID){ + CFStringAppend(description, CFSTR(", ")); + CFStringAppend(description, ID); + } + + pi->deviceID = CFStringCreateCopy(kCFAllocatorDefault, description); + CFReleaseNull(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->transportType = CFStringCreateCopy(allocator, toCopy->transportType); + pi->deviceID = CFStringCreateCopy(kCFAllocatorDefault, toCopy->deviceID); + 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 + pi->transportType = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("KVS")); + *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) { + if (CFGetTypeID(versionNumber) != CFNumberGetTypeID()) { + CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(versionNumber)); + SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL, + CFSTR("Expected (version) number got %@"), description); + CFReleaseSafe(description); + goto fail; + } + CFNumberGetValue(versionNumber, kCFNumberCFIndexType, &pi->version); + } + + CFDictionaryRef gestalt = CFDictionaryGetValue(pi->description, sGestaltKey); + + if (gestalt == NULL) { + SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL, + CFSTR("gestalt key missing")); + goto fail; + } + + if (!isDictionary(gestalt)) { + CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(gestalt)); + 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; + } + + CFStringRef deviceName = CFDictionaryGetValue(pi->gestalt, kPIUserDefinedDeviceName); + CFStringRef modelName = CFDictionaryGetValue(pi->gestalt, kPIDeviceModelName); + CFStringRef ID = pi->id; + CFMutableStringRef description = CFStringCreateMutableCopy(kCFAllocatorDefault, CFStringGetLength(deviceName), deviceName); + if(modelName){ + CFStringAppend(description, CFSTR(", ")); + CFStringAppend(description, modelName); + } + if(ID){ + CFStringAppend(description, CFSTR(", ")); + CFStringAppend(description, ID); + } + + pi->deviceID = CFStringCreateCopy(kCFAllocatorDefault, description); + CFReleaseNull(description); + 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); + CFReleaseNull(pi->deviceID); +} + +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) { + // The code below is necessary but not sufficient; not returning a CFComparisonResult + // It probably is OK to say that a NULL is < + if (val1 == NULL || val2 == NULL) { + ptrdiff_t dv = val1 - val2; + return dv < 0 ? kCFCompareLessThan : dv == 0 ? kCFCompareEqualTo : kCFCompareGreaterThan; + } + + CFStringRef v1 = SOSPeerInfoGetPeerID((SOSPeerInfoRef) val1); + CFStringRef v2 = SOSPeerInfoGetPeerID((SOSPeerInfoRef) val2); + if (v1 == NULL || v2 == NULL) { + ptrdiff_t dv = (const void *)v1 - (const void *)v2; + return dv < 0 ? kCFCompareLessThan : dv == 0 ? kCFCompareEqualTo : kCFCompareGreaterThan; + } + + 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; + CFIndex version = SOSPeerInfoGetPeerProtocolVersion(pi); + + return CFStringCreateWithFormat(NULL, NULL, CFSTR(""), + pi, + SOSPeerInfoIsRetirementTicket(pi) ? "R" : "-", + CFDictionaryGetValue(pi->gestalt, kPIUserDefinedDeviceName), + CFDictionaryGetValue(pi->gestalt, kPIDeviceModelName), + pi->id, + version); +} + +CFDictionaryRef SOSPeerInfoCopyPeerGestalt(SOSPeerInfoRef pi) { + CFRetain(pi->gestalt); + return pi->gestalt; +} + +CFStringRef SOSPeerInfoGetTransportType(SOSPeerInfoRef peer){ + return peer->transportType; +} + +CFStringRef SOSPeerInfoGetDeviceID(SOSPeerInfoRef peer){ + return peer->deviceID; +} + +void SOSPeerInfoSetDeviceID(SOSPeerInfoRef peer, CFStringRef IDS){ + CFReleaseNull(peer->deviceID); + peer->deviceID = CFStringCreateCopy(kCFAllocatorDefault, IDS); +} + +CFStringRef SOSPeerInfoGetPeerName(SOSPeerInfoRef peer) { + return SOSPeerInfoLookupGestaltValue(peer, kPIUserDefinedDeviceName); +} + +CFStringRef SOSPeerInfoGetPeerDeviceType(SOSPeerInfoRef peer) { + return SOSPeerInfoLookupGestaltValue(peer, kPIDeviceModelName); +} + +CFIndex SOSPeerInfoGetPeerProtocolVersion(SOSPeerInfoRef peer) { + CFIndex version = 0; + CFTypeRef val = SOSPeerInfoLookupGestaltValue(peer, kPIMessageProtocolVersion); + if (val && CFGetTypeID(val) == CFNumberGetTypeID()) + CFNumberGetValue(val, kCFNumberCFIndexType, &version); + return version; +} + +CFTypeRef SOSPeerInfoLookupGestaltValue(SOSPeerInfoRef pi, CFStringRef key) { + return CFDictionaryGetValue(pi->gestalt, key); +} + +CFStringRef SOSPeerInfoGetPeerID(SOSPeerInfoRef pi) { + return pi ? pi->id : NULL; +} + +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); + pi->transportType = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("KVS")); + 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; +} + + +// +// 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; +} +