我正在尝试按照以下教程实现新的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;
}
}
我发现沙箱收据有三个问题应该没问题: