Skip to content

Instantly share code, notes, and snippets.

@redsweater
Forked from mattstevens/AppStoreReceipt.h
Created October 19, 2011 05:23
Show Gist options
  • Save redsweater/1297523 to your computer and use it in GitHub Desktop.
Save redsweater/1297523 to your computer and use it in GitHub Desktop.
#import <Cocoa/Cocoa.h>
BOOL VerifyAppStoreReceipt();
BOOL VerifyAppStoreReceiptData(NSData *data);
NSURL *BackupReceiptURL();
void BackupAppStoreReceipt();
NSData *MACAddressData();
NSDictionary *DictionaryFromAppStoreReceipt(NSData *fullData);
#import "AppStoreReceipt.h"
#import <Security/Security.h>
#import <Security/SecAsn1Coder.h>
#import <Security/SecAsn1Templates.h>
#include <CommonCrypto/CommonDigest.h>
NSString *kReceiptBundleIdentifier = @"BundleIdentifier";
NSString *kReceiptBundleIdentifierData = @"BundleIdentifierData";
NSString *kReceiptVersion = @"Version";
NSString *kReceiptOpaqueValue = @"OpaqueValue";
NSString *kReceiptHash = @"Hash";
enum ATTRIBUTES
{
ATTR_START = 1,
BUNDLE_ID,
VERSION,
OPAQUE_VALUE,
HASH,
ATTR_END
};
typedef struct {
CSSM_SIZE length;
uint8_t *data;
} Asn1Data;
typedef struct {
Asn1Data type;
Asn1Data version;
Asn1Data value;
} ReceiptAttribute;
typedef struct {
ReceiptAttribute** attributes;
} Receipt;
const SecAsn1Template kReceiptAttributeTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ReceiptAttribute) },
{ SEC_ASN1_INTEGER, offsetof(ReceiptAttribute, type), },
{ SEC_ASN1_INTEGER, offsetof(ReceiptAttribute, version), },
{ SEC_ASN1_OCTET_STRING, offsetof(ReceiptAttribute, value), },
{ 0 }
};
const SecAsn1Template kReceiptTemplate[] = {
{ SEC_ASN1_SET_OF, offsetof(Receipt, attributes), kReceiptAttributeTemplate, sizeof(Receipt) }
};
BOOL VerifyAppStoreReceipt() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *backupURL = BackupReceiptURL();
NSURL *url = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:url];
if (!receiptData) {
// Try the backup receipt
url = backupURL;
receiptData = [NSData dataWithContentsOfURL:url];
}
BOOL result = VerifyAppStoreReceiptData(receiptData);
[pool release];
return result;
}
BOOL VerifyAppStoreReceiptData(NSData *data) {
NSDictionary *receipt = DictionaryFromAppStoreReceipt(data);
if (!receipt)
return NO;
NSData *addressData = MACAddressData();
NSMutableData *input = [NSMutableData data];
[input appendData:addressData];
[input appendData:[receipt objectForKey:kReceiptOpaqueValue]];
[input appendData:[receipt objectForKey:kReceiptBundleIdentifierData]];
NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH];
CC_SHA1([input bytes], [input length], [hash mutableBytes]);
if ([hash isEqualToData:[receipt objectForKey:kReceiptHash]] == NO) {
return NO;
}
return YES;
}
NSURL *BackupReceiptURL() {
NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
NSString *backupPath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
backupPath = [backupPath stringByAppendingPathComponent:appName];
backupPath = [backupPath stringByAppendingPathComponent:@"receipt"];
return [NSURL fileURLWithPath:backupPath];
}
void BackupAppStoreReceipt() {
NSURL *url = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *data = [NSData dataWithContentsOfURL:url];
NSURL *backupURL = BackupReceiptURL();
NSData *backupData = [NSData dataWithContentsOfURL:backupURL];
if (data && [backupData isEqualToData:data] == NO) {
NSURL *appSupportURL = [backupURL URLByDeletingLastPathComponent];
[[NSFileManager defaultManager] createDirectoryAtURL:appSupportURL withIntermediateDirectories:YES attributes:nil error:nil];
[data writeToURL:backupURL atomically:YES];
}
}
NSData *MACAddressData() {
kern_return_t kernResult;
mach_port_t master_port;
CFMutableDictionaryRef matchingDict;
io_iterator_t iterator;
io_object_t service;
CFDataRef macAddress = nil;
kernResult = IOMasterPort(MACH_PORT_NULL, &master_port);
if (kernResult != KERN_SUCCESS) {
return nil;
}
matchingDict = IOBSDNameMatching(master_port, 0, "en0");
if (!matchingDict) {
return nil;
}
kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator);
if (kernResult != KERN_SUCCESS) {
return nil;
}
while((service = IOIteratorNext(iterator)) != 0) {
io_object_t parentService;
kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService);
if (kernResult == KERN_SUCCESS) {
if (macAddress) CFRelease(macAddress);
macAddress = (CFDataRef)IORegistryEntryCreateCFProperty(parentService, CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
IOObjectRelease(parentService);
}
IOObjectRelease(iterator);
IOObjectRelease(service);
}
return [(NSData *)macAddress autorelease];
}
NSDictionary *DictionaryFromAppStoreReceipt(NSData *fullData) {
OSStatus err;
CMSDecoderRef decoder;
err = CMSDecoderCreate(&decoder);
if (err != noErr)
return nil;
err = CMSDecoderUpdateMessage(decoder, [fullData bytes], [fullData length]);
if (err != noErr)
return nil;
err = CMSDecoderFinalizeMessage(decoder);
if (err != noErr)
return nil;
CFDataRef dataRef;
CMSDecoderCopyContent(decoder, &dataRef);
NSData *receiptData = [(NSData *)dataRef autorelease];
CFRelease(decoder);
NSMutableDictionary *info = [NSMutableDictionary dictionary];
SecAsn1CoderRef coder;
SecAsn1CoderCreate(&coder);
Receipt receipt = {0};
err = SecAsn1Decode(coder, [receiptData bytes], [receiptData length], kReceiptTemplate, &receipt);
if (err != noErr || receipt.attributes == NULL)
return nil;
for (int i = 0; receipt.attributes[i] != NULL; i++) {
ReceiptAttribute *attr = receipt.attributes[i];
if (attr->type.length < 1 || attr->value.length < 1)
continue;
NSString *key;
int type = attr->type.data[0];
if (type <= ATTR_START || type >= ATTR_END)
continue;
// Bytes
if (type == BUNDLE_ID || type == OPAQUE_VALUE || type == HASH) {
NSData *data = [NSData dataWithBytes:attr->value.data length:(NSUInteger)attr->value.length];
switch (type) {
case BUNDLE_ID:
// Included for hash generation
key = kReceiptBundleIdentifierData;
break;
case OPAQUE_VALUE:
key = kReceiptOpaqueValue;
break;
case HASH:
key = kReceiptHash;
break;
}
[info setObject:data forKey:key];
}
// Strings
if (type == BUNDLE_ID || type == VERSION) {
Asn1Data raw_string;
err = SecAsn1Decode(coder, attr->value.data, attr->value.length, kSecAsn1UTF8StringTemplate, &raw_string);
if (err == 0) {
NSString *string = [[[NSString alloc] initWithBytes:raw_string.data
length:(NSUInteger)raw_string.length
encoding:NSUTF8StringEncoding] autorelease];
switch (type) {
case BUNDLE_ID:
key = kReceiptBundleIdentifier;
break;
case VERSION:
key = kReceiptVersion;
break;
}
[info setObject:string forKey:key];
}
}
}
SecAsn1CoderRelease(coder);
return info;
// This takes a while, should obtain secTrustOut and verify asynchronously or copy the certs from
// the CMS message and look for an Apple root.
/*SecPolicyRef policy = SecPolicyCreateWithOID(kSecPolicyMacAppStoreReceipt); //SecPolicyCreateBasicX509();
CMSSignerStatus signerStatus = 0;
SecTrustRef secTrust = NULL;
OSStatus verifyResultCode = 0;
err = CMSDecoderCopySignerStatus(decoder, 0, policy, TRUE, &signerStatus, NULL, &verifyResultCode);*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment