#include #include #include #include #include #include "CSBlob.h" #include "MachO.h" #include "MemoryStream.h" #include "FileStream.h" #include "MachOByteOrder.h" #include "Entitlements.h" CFDataRef get_property_list_data(CFPropertyListRef plist) { CFDataRef data = NULL; CFErrorRef error = NULL; data = CFPropertyListCreateData( kCFAllocatorDefault, plist, kCFPropertyListXMLFormat_v1_0, 0, &error ); if (data == NULL) { if (error != NULL) { CFShow(error); CFRelease(error); } } return data; } CFPropertyListRef create_property_list_from_file(const char *xmlFile) { FILE *file = fopen(xmlFile, "rb"); if (file == NULL) { fprintf(stderr, "Error: failed to open entitlements file\n"); return NULL; } fseek(file, 0, SEEK_END); size_t entitlementsSize = ftell(file); fseek(file, 0, SEEK_SET); void *entitlements = malloc(entitlementsSize); if (entitlements == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlements file\n"); fclose(file); return NULL; } if (fread(entitlements, 1, entitlementsSize, file) != entitlementsSize) { fprintf(stderr, "Error: failed to read entitlements file\n"); free(entitlements); fclose(file); return NULL; } fclose(file); CFDataRef entitlementsData = CFDataCreate(NULL, entitlements, entitlementsSize); if (entitlementsData == NULL) { fprintf(stderr, "Error: failed to create CFData from entitlements file\n"); free(entitlements); return NULL; } CFPropertyListFormat format; CFErrorRef error; CFPropertyListRef entitlementsPlist = CFPropertyListCreateWithData(NULL, entitlementsData, kCFPropertyListMutableContainersAndLeaves, &format, &error); if (entitlementsPlist == NULL) { fprintf(stderr, "Error: failed to parse entitlements file\n"); CFRelease(entitlementsData); CFRelease(error); free(entitlements); return NULL; } CFRelease(entitlementsData); free(entitlements); return entitlementsPlist; } CS_DecodedBlob *create_xml_entitlements_blob(const char *entitlementsFile) { CFPropertyListRef entitlementsPlist = create_property_list_from_file(entitlementsFile); if (entitlementsPlist == NULL) { fprintf(stderr, "Error: failed to create property list from entitlements file\n"); return NULL; } CFDataRef entitlementsPlistData = get_property_list_data(entitlementsPlist); if (entitlementsPlistData == NULL) { fprintf(stderr, "Error: failed to convert entitlements property list to data\n"); CFRelease(entitlementsPlist); return NULL; } uint32_t *genericBlob = malloc(sizeof(CSMAGIC_EMBEDDED_ENTITLEMENTS) + sizeof(uint32_t) + CFDataGetLength(entitlementsPlistData)); if (genericBlob == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlements blob\n"); CFRelease(entitlementsPlist); CFRelease(entitlementsPlistData); return NULL; } // Messy, but it works *genericBlob = HOST_TO_BIG(CSMAGIC_EMBEDDED_ENTITLEMENTS); uint32_t totalSize = sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + CFDataGetLength(entitlementsPlistData); *(genericBlob + 1) = HOST_TO_BIG(totalSize); memcpy((char *)genericBlob + sizeof(CSMAGIC_EMBEDDED_ENTITLEMENTS) + sizeof(uint32_t), CFDataGetBytePtr(entitlementsPlistData), CFDataGetLength(entitlementsPlistData)); CS_DecodedBlob *entitlementsBlob = csd_blob_init(CSSLOT_ENTITLEMENTS, (CS_GenericBlob *)genericBlob); if (entitlementsBlob == NULL) { fprintf(stderr, "Error: failed to create entitlements blob\n"); free(genericBlob); } CFRelease(entitlementsPlist); CFRelease(entitlementsPlistData); return entitlementsBlob; } enum EntitlementType { ENTITLEMENT_TYPE_BOOL, ENTITLEMENT_TYPE_INT, ENTITLEMENT_TYPE_STRING, ENTITLEMENT_TYPE_ARRAY, }; enum ArrayType { ARRAY_TYPE_STRING, ARRAY_TYPE_INT, }; typedef struct { uint32_t nItems; enum ArrayType type; void *items; } EntitlementArray; typedef struct { enum EntitlementType type; union { bool boolValue; int intValue; char *stringValue; EntitlementArray arrayValue; }; } EntitlementValue; typedef struct { char *key; EntitlementValue value; } Entitlement; typedef struct { uint32_t nEntitlements; Entitlement *entitlements; } Entitlements; void print_entitlement_information(Entitlement *entitlement) { printf("Key: %s\n", entitlement->key); switch (entitlement->value.type) { case ENTITLEMENT_TYPE_BOOL: printf("Value: %s\n", entitlement->value.boolValue ? "true" : "false"); break; case ENTITLEMENT_TYPE_INT: printf("Value: %d\n", entitlement->value.intValue); break; case ENTITLEMENT_TYPE_STRING: printf("Value: %s\n", entitlement->value.stringValue); break; case ENTITLEMENT_TYPE_ARRAY: printf("Values:\n"); for (int i = 0; i < entitlement->value.arrayValue.nItems; i++) { printf("%d: %s\n", i + 1, ((char **)entitlement->value.arrayValue.items)[i]); } break; } } DEREncodedItem *der_encode_array(EntitlementArray array) { // Encode each item in the array, then create a sequence of the items DEREncodedItem **items = malloc(sizeof(DEREncodedItem *) * array.nItems); for (int i = 0; i < array.nItems; i++) { // Encode the item if (array.type == ARRAY_TYPE_STRING) { items[i] = der_encode_utf8_string(((char **)array.items)[i]); } else { items[i] = der_encode_integer(((int *)array.items)[i]); } } // Create a sequence of the items DEREncodedItem *sequence = der_encode_sequence(items, array.nItems); if (sequence == NULL) { fprintf(stderr, "Error: failed to encode sequence\n"); return NULL; } // Free the encoded items for (int i = 0; i < array.nItems; i++) { der_free_encoded_item(items[i]); } free(items); return sequence; } DEREncodedItem *der_encode_entitlement(Entitlement entitlement) { if (!entitlement.key) { fprintf(stderr, "Error: missing entitlement key\n"); return NULL; } DEREncodedItem *key = der_encode_utf8_string(entitlement.key); if (key == NULL) { fprintf(stderr, "Error: failed to encode entitlement key\n"); return NULL; } DEREncodedItem *value = NULL; switch (entitlement.value.type) { case ENTITLEMENT_TYPE_BOOL: value = der_encode_boolean(entitlement.value.boolValue); break; case ENTITLEMENT_TYPE_INT: value = der_encode_integer(entitlement.value.intValue); break; case ENTITLEMENT_TYPE_STRING: value = der_encode_utf8_string(entitlement.value.stringValue); break; case ENTITLEMENT_TYPE_ARRAY: value = der_encode_array(entitlement.value.arrayValue); break; } if (value == NULL) { fprintf(stderr, "Error: failed to encode entitlement value\n"); der_free_encoded_item(key); return NULL; } DEREncodedItem *items[] = {key, value}; DEREncodedItem *sequence = der_encode_sequence(items, 2); if (sequence == NULL) { fprintf(stderr, "Error: failed to encode sequence\n"); der_free_encoded_item(key); der_free_encoded_item(value); return NULL; } der_free_encoded_item(key); der_free_encoded_item(value); return sequence; } DEREncodedItem *der_encode_entitlements(DEREncodedItem *entitlements[], uint32_t nEntitlements) { DEREncodedItem *entitlementsSet = der_encode_set(entitlements, nEntitlements); if (entitlementsSet == NULL) { fprintf(stderr, "Error: failed to encode set of entitlements\n"); return NULL; } for(int i = 0; i < nEntitlements; i++) { der_free_encoded_item(entitlements[i]); } return entitlementsSet; } static void append_to_entitlements(const void* key, const void* value, void* context) { Entitlements *entitlements = (Entitlements *)context; CFStringRef keyString = (CFStringRef)key; CFIndex keyLength = CFStringGetLength(keyString); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(keyLength, kCFStringEncodingUTF8); char *keyCString = malloc(maxSize); if (keyCString == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlement key\n"); return; } if (!CFStringGetCString(keyString, keyCString, maxSize, kCFStringEncodingUTF8)) { fprintf(stderr, "Error: failed to convert entitlement key to C string\n"); free(keyCString); return; } EntitlementValue entitlementValue; CFTypeID valueType = CFGetTypeID(value); if (valueType == CFBooleanGetTypeID()) { entitlementValue.type = ENTITLEMENT_TYPE_BOOL; entitlementValue.boolValue = CFBooleanGetValue((CFBooleanRef)value); } else if (valueType == CFNumberGetTypeID()) { entitlementValue.type = ENTITLEMENT_TYPE_INT; CFNumberGetValue((CFNumberRef)value, kCFNumberSInt32Type, &entitlementValue.intValue); } else if (valueType == CFStringGetTypeID()) { entitlementValue.type = ENTITLEMENT_TYPE_STRING; CFIndex valueLength = CFStringGetLength((CFStringRef)value); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8); char *valueCString = malloc(maxSize); if (valueCString == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlement value\n"); free(keyCString); return; } if (!CFStringGetCString((CFStringRef)value, valueCString, maxSize, kCFStringEncodingUTF8)) { fprintf(stderr, "Error: failed to convert entitlement value to C string\n"); free(keyCString); free(valueCString); return; } entitlementValue.stringValue = valueCString; } else if (valueType == CFArrayGetTypeID()) { entitlementValue.type = ENTITLEMENT_TYPE_ARRAY; CFArrayRef array = (CFArrayRef)value; CFIndex nItems = CFArrayGetCount(array); char **items = malloc(sizeof(char *) * nItems); if (items == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlement array\n"); free(keyCString); return; } for (int i = 0; i < nItems; i++) { CFStringRef item = (CFStringRef)CFArrayGetValueAtIndex(array, i); CFIndex itemLength = CFStringGetLength(item); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(itemLength, kCFStringEncodingUTF8); char *itemCString = malloc(maxSize); if (itemCString == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlement\n"); for (int j = 0; j < i; j++) { free(items[j]); } free(items); free(keyCString); return; } if (!CFStringGetCString(item, itemCString, maxSize, kCFStringEncodingUTF8)) { fprintf(stderr, "Error: failed to convert entitlement to C string\n"); for (int j = 0; j < i; j++) { free(items[j]); } free(items); free(keyCString); free(itemCString); return; } items[i] = itemCString; } EntitlementArray arrayValue = { .nItems = nItems, .type = ARRAY_TYPE_STRING, .items = items, }; entitlementValue.arrayValue = arrayValue; } else { fprintf(stderr, "Error: unknown entitlement value type\n"); free(keyCString); return; } Entitlement newEntitlement = { .key = keyCString, .value = entitlementValue, }; // print_entitlement_information(&newEntitlement); entitlements->entitlements[entitlements->nEntitlements] = newEntitlement; entitlements->nEntitlements++; return; } Entitlements *parse_entitlements_from_property_list(const char *entitlementsFile) { CFPropertyListRef entitlementsPlist = create_property_list_from_file(entitlementsFile); if (entitlementsPlist == NULL) { fprintf(stderr, "Error: failed to create property list from entitlements file\n"); return NULL; } if (CFGetTypeID(entitlementsPlist) != CFDictionaryGetTypeID()) { fprintf(stderr, "Error: entitlements file is not a dictionary\n"); CFRelease(entitlementsPlist); return NULL; } CFDictionaryRef entitlementsDict = (CFDictionaryRef)entitlementsPlist; CFIndex nEntitlements = CFDictionaryGetCount(entitlementsDict); Entitlements *entitlements = malloc(sizeof(Entitlements)); if (entitlements == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlements\n"); CFRelease(entitlementsPlist); return NULL; } entitlements->nEntitlements = 0; entitlements->entitlements = malloc(sizeof(Entitlement) * nEntitlements); if (entitlements->entitlements == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlements\n"); CFRelease(entitlementsPlist); free(entitlements); return NULL; } CFDictionaryApplyFunction(entitlementsDict, append_to_entitlements, entitlements); CFRelease(entitlementsPlist); return entitlements; } DEREncodedItem *encode_entitlements_property_list(const char *entitlementsFile) { Entitlements *entitlements = parse_entitlements_from_property_list(entitlementsFile); if (entitlements == NULL) { fprintf(stderr, "Error: failed to parse entitlements\n"); return NULL; } printf("Parsed %d entitlements from entitlements file.\n", entitlements->nEntitlements); DEREncodedItem **encodedEntitlements = malloc(sizeof(DEREncodedItem *) * entitlements->nEntitlements); if (encodedEntitlements == NULL) { fprintf(stderr, "Error: failed to allocate memory for encoded entitlements\n"); free(entitlements->entitlements); free(entitlements); return NULL; } for (int i = 0; i < entitlements->nEntitlements; i++) { encodedEntitlements[i] = der_encode_entitlement(entitlements->entitlements[i]); if (encodedEntitlements[i] == NULL) { fprintf(stderr, "Error: failed to encode entitlement\n"); for (int j = 0; j < i; j++) { der_free_encoded_item(encodedEntitlements[j]); } free(encodedEntitlements); free(entitlements->entitlements); free(entitlements); return NULL; } } DEREncodedItem *entitlementsSet = der_encode_entitlements(encodedEntitlements, entitlements->nEntitlements); if (entitlementsSet == NULL) { fprintf(stderr, "Error: failed to encode entitlements\n"); for (int i = 0; i < entitlements->nEntitlements; i++) { der_free_encoded_item(encodedEntitlements[i]); } free(encodedEntitlements); free(entitlements->entitlements); free(entitlements); return NULL; } free(encodedEntitlements); free(entitlements->entitlements); free(entitlements); return entitlementsSet; } CS_DecodedBlob *create_der_entitlements_blob(const char *entitlementsFile) { DEREncodedItem *entitlements = encode_entitlements_property_list(entitlementsFile); if (entitlements == NULL) { fprintf(stderr, "Error: failed to encode entitlements\n"); return NULL; } uint32_t *genericBlob = malloc(sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + entitlements->length); if (genericBlob == NULL) { fprintf(stderr, "Error: failed to allocate memory for entitlements blob\n"); der_free_encoded_item(entitlements); return NULL; } *genericBlob = HOST_TO_BIG(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS); uint32_t totalSize = sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + entitlements->length; *(genericBlob + 1) = HOST_TO_BIG(totalSize); memcpy((char *)genericBlob + sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t), entitlements->data, entitlements->length); CS_DecodedBlob *entitlementsBlob = csd_blob_init(CSSLOT_ENTITLEMENTS, (CS_GenericBlob *)genericBlob); if (entitlementsBlob == NULL) { fprintf(stderr, "Error: failed to create entitlements blob\n"); free(genericBlob); } der_free_encoded_item(entitlements); return entitlementsBlob; }