处理[[NSBundle mainBundle] appStoreReceiptURL]

时间:2014-12-29 15:07:25

标签: ios validation openssl in-app-purchase

我正在尝试按照以下教程实现新的inApp验证机制: Receipt Validation 我设法安装openSSL和所有爵士乐,但是当我执行function:

-(void) processReceiptUrl:(NSURL*)receiptUrl{
BOOL success=YES;
NSArray *downloads = nil;

#ifdef __IPHONE_6_0

if([transaction respondsToSelector:@selector(downloads)])
    downloads = transaction.downloads;

if([downloads count] > 0) {

    [[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
    // We don't have content yet, and we can't finish the transaction
#ifndef NDEBUG
    NSLog(@"Download(s) started: %@", [transaction description]);
#endif
    return;
}
#endif

NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
// Create a memory buffer to extract the PKCS #7 container
BIO *receiptBIO = BIO_new(BIO_s_mem());
BIO_write(receiptBIO, [receiptData bytes], (int) [receiptData length]);
PKCS7 *receiptPKCS7 =d2i_PKCS7_bio(receiptBIO, NULL);
if (!receiptPKCS7) {
    NSLog(@"no receipt");
    success=NO;
}

// Check that the container has a signature
if (!PKCS7_type_is_signed(receiptPKCS7)) {
    NSLog(@"unsigned");
}

// Check that the signed container has actual data
if (!PKCS7_type_is_data(receiptPKCS7->d.sign->contents)) {
   success=NO;
}
// Load the Apple Root CA (downloaded from https://www.apple.com/certificateauthority/)
NSURL *appleRootURL = [[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"];
NSData *appleRootData = [NSData dataWithContentsOfURL:appleRootURL];
BIO *appleRootBIO = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(appleRootBIO, 0);
BIO_write(appleRootBIO, (const void *) [appleRootData bytes], (int) [appleRootData length]);
X509 *appleRootX509 = d2i_X509_bio(appleRootBIO, NULL);

// Create a certificate store
X509_STORE *store = X509_STORE_new();
X509_STORE_add_cert(store, appleRootX509);

// Be sure to load the digests before the verification
OpenSSL_add_all_digests();
//OpenSSL_add_all_algorithms();
// Check the signature
int result = PKCS7_verify(receiptPKCS7, NULL, store, NULL, NULL, 0);
if (result != 1) {
    NSLog(@"error verify: %lu", ERR_get_error());
    //success=NO;
}

ASN1_OCTET_STRING *octets = receiptPKCS7->d.sign->contents->d.data;
const unsigned char *ptr = octets->data;
const unsigned char *end = ptr + octets->length;
const unsigned char *str_ptr;

int type = 0, str_type = 0;
int xclass = 0, str_xclass = 0;
long length = 0, str_length = 0;

// Store for the receipt information
NSString *bundleIdString = nil;
NSString *bundleVersionString = nil;
NSData *bundleIdData = nil;
NSData *hashData = nil;
NSData *opaqueData = nil;
NSDate *expirationDate = nil;

// Date formatter to handle RFC 3339 dates in GMT time zone
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

// Decode payload (a SET is expected)
ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr);
if (type != V_ASN1_SET) {
    success=NO;
}
NSData* iTunesValidationReceipt;
while (ptr < end) {
    ASN1_INTEGER *integer;

    // Parse the attribute sequence (a SEQUENCE is expected)
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr);
    if (type != V_ASN1_SEQUENCE) {
        success=NO;
    }

    const unsigned char *seq_end = ptr + length;
    long attr_type = 0;
    long attr_version = 0;

    // Parse the attribute type (an INTEGER is expected)
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr);
    if (type != V_ASN1_INTEGER) {
        success=NO;
    }
    integer = c2i_ASN1_INTEGER(NULL, &ptr, length);
    attr_type = ASN1_INTEGER_get(integer);
    ASN1_INTEGER_free(integer);

    // Parse the attribute version (an INTEGER is expected)
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr);
    if (type != V_ASN1_INTEGER) {
        success=NO;
    }
    integer = c2i_ASN1_INTEGER(NULL, &ptr, length);
    attr_version = ASN1_INTEGER_get(integer);
    ASN1_INTEGER_free(integer);

    // Check the attribute value (an OCTET STRING is expected)
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr);
    if (type != V_ASN1_OCTET_STRING) {
        success=NO;
    }
    NSLog(@"attrtype=%ld", attr_type);
    switch (attr_type) {
        case 2:
            // Bundle identifier
            str_ptr = ptr;
            ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr);
            if (str_type == V_ASN1_UTF8STRING) {
                // We store both the decoded string and the raw data for later
                // The raw is data will be used when computing the GUID hash
                bundleIdString = [[NSString alloc] initWithBytes:str_ptr length:str_length encoding:NSUTF8StringEncoding];
                bundleIdData = [[NSData alloc] initWithBytes:(const void *)ptr length:length];
            }
            break;

        case 3:
            // Bundle version
            str_ptr = ptr;
            ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr);
            if (str_type == V_ASN1_UTF8STRING) {
                // We store the decoded string for later
                bundleVersionString = [[NSString alloc] initWithBytes:str_ptr length:str_length encoding:NSUTF8StringEncoding];
            }
            break;

        case 4:
            // Opaque value
            opaqueData = [[NSData alloc] initWithBytes:(const void *)ptr length:length];
            break;

        case 5:
            // Computed GUID (SHA-1 Hash)
            hashData = [[NSData alloc] initWithBytes:(const void *)ptr length:length];
            break;

        case 17:
            str_ptr = ptr;
            ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr);
            iTunesValidationReceipt = [[NSData alloc] initWithBytes:(const void *)ptr length:length];
            NSLog(@"iTunes certificate %@", iTunesValidationReceipt);
            break;

        case 21:
            // Expiration date
            str_ptr = ptr;
            ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr);
            if (str_type == V_ASN1_IA5STRING) {
                // The date is stored as a string that needs to be parsed
                NSString *dateString = [[NSString alloc] initWithBytes:str_ptr length:str_length encoding:NSASCIIStringEncoding];
                expirationDate = [formatter dateFromString:dateString];
            }
            break;

            // You can parse more attributes...

        default:
            break;
    }

    // Move past the value
    ptr += length;
}
// Be sure that all information is present
if (bundleIdString == nil ||
    bundleVersionString == nil ||
    opaqueData == nil ||
    hashData == nil) {
    success=NO;
}

NSString *appId=[[NSBundle mainBundle] bundleIdentifier];
NSLog(@"prodName=%@ bundleName=%@", appId, bundleIdString);
if (![bundleIdString isEqualToString:appId]) {
    NSLog(@"not my app");
    success=NO;
}
// Check the bundle version
NSString *majorVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSLog(@"majorVersion=%@ bundleVersion=%@", majorVersion, bundleVersionString);
if (![bundleVersionString isEqualToString:majorVersion]) {
    NSLog(@"not current release");
    success=NO;
}
UIDevice *device = [UIDevice currentDevice];
NSUUID *uuid = [device identifierForVendor];
NSData *guidData = [NSData dataWithBytes:(const void *)uuid length:16];
unsigned char hash[20];

// Create a hashing context for computation
SHA_CTX ctx;
SHA1_Init(&ctx);
SHA1_Update(&ctx, [guidData bytes], (size_t) [guidData length]);
SHA1_Update(&ctx, [opaqueData bytes], (size_t) [opaqueData length]);
SHA1_Update(&ctx, [bundleIdData bytes], (size_t) [bundleIdData length]);
SHA1_Final(hash, &ctx);

// Do the comparison
NSData *computedHashData = [NSData dataWithBytes:hash length:20];
NSLog(@"%@\n-----%@", computedHashData, hashData);
if (![computedHashData isEqualToData:hashData]) {
    NSLog(@"validation fails for hash");
    //success=NO;
}
// If an expiration date is present, check it
if (expirationDate) {
    NSDate *currentDate = [NSDate date];
    if ([expirationDate compare:currentDate] == NSOrderedAscending) {
         NSLog(@"volume purchased fails");
        success=NO;
    }
}

我发现沙箱收据有三个问题应该没问题:

  1. 在初始PKCS7_verify(receiptPKCS7,NULL,store,NULL,NULL,0);返回218570875
  2. 计算出的hashData和从Receipt获取的hashData是不同的,最后是
  3. 实际上21002的iTunes Store无法识别第17项最后一次迭代所获得的值

0 个答案:

没有答案