我认为说Apple的收据验证帮助是混淆的是轻描淡写。
不知何故,我已经能够将一些不使用OpenSSL或ASN1的代码放在一起,以便让我可以访问收据字段作为捆绑包的所有收据的可读字符串(包括最新的可能不包含的字符串)甚至由当前设备生成。)
这是一项正在进行中的工作,正如你可以看到的那样,但有人可以告诉我为什么我不应该使用这种方法,因为从我读过的所有内容看起来似乎太容易了?
还有人可以帮助我做todo的吗?就像我需要在todo1和2做的那样(我想我可以处理3,4和5)?
在我的情况下,我正在扫描“latest_receipt_info”'从最近到最早的收据,直到我找到我感兴趣的应用内商品_id,然后从'expires_date_ms'确定其过期状态。这是确定product_id当前过期状态的正确方法吗?
无论如何它是:
NSURL *storeURL;
exms = [[NSUserDefaults standardUserDefaults] doubleForKey:@"exms"]; //globally defined elsewhere
[[NSUserDefaults standardUserDefaults] synchronize];
NSDate *today = [NSDate date];
int64_t nowms = 1000*[today timeIntervalSince1970];
if (nowms> exms){ //todo maybe give a week grace here
isSubscribed= NO; //globally defined elsewhere
// Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) { /* No local receipt -- handle the error. */ } //todo1
/* ... Now Send the receipt data to server ... */
// Create the JSON object that describes the request
NSError *error;
NSDictionary *requestContents = @{@"receipt-data": [receipt base64EncodedStringWithOptions:0],
@"password": @"put your shared secret here"};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) { /* ... Handle error ... */ } //todo2
NSString *file=[NSHomeDirectory() stringByAppendingPathComponent:@"iTunesMetadata.plist"];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
// probably a store app
storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]; //todo3 test this for sure rather than ifdef debug
}else{
storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
}
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */ //todo4
} else {
int64_t edms= 0;
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) { /* ... Handle error ...*/ } //todo5
// Get object from the root object
NSArray *dictionaryObject = (NSArray *)[jsonResponse objectForKey:@"latest_receipt_info"];
NSDictionary *rcpt;
for (int i=[dictionaryObject count]-1;i>-1;i--){
rcpt= [dictionaryObject objectAtIndex:i];
NSString *pid= [rcpt objectForKey:@"product_id"];
if ([pid isEqualToString:@"put your in-app purchase you are interested in here"]){
edms= [[rcpt objectForKey:@"expires_date_ms"] longLongValue];
break;
}
}
NSDate *today = [NSDate date];
int64_t nowms = 1000*[today timeIntervalSince1970];
if (nowms> edms){
isSubscribed= NO;
}else{
isSubscribed= YES;
}
exms= edms;
[[NSUserDefaults standardUserDefaults] setDouble:exms forKey:@"exms"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (isSubscribed== YES) _isexpiredView.hidden= true;
}
}];
}else{
isSubscribed= YES;
}
答案 0 :(得分:0)
收据验证的机械步骤并不难,您的实施解决了这些问题。由于您无法信任最终用户设备,因此安全地执行此操作会更加困难。
我看到的第一个问题是您将过期日期存储在NSUserDefaults中,并且只有在“now”在过期日期之后才验证收据。因此,我可以简单地将过期日期放到NSUserDefaults中,并且您永远不会使订阅到期,也不会检查过期日期是否有效。使用加密值并存储在钥匙串中会使其更安全。
其次,您将直接从设备验证收据到Apple服务器。来自Receipt Validation Programming Guide
使用受信任的服务器与App Store进行通信。用你自己的 服务器允许您设计您的应用程序以识别和信任您的 服务器,并确保您的服务器与应用程序连接 存储服务器。无法在它们之间建立可信连接 用户的设备和App Store直接因为您无法控制 这个连接的任何一端。
这就是说,通过直接从您的设备连接,您可以轻松地欺骗Apple服务器。如果您确认收据是由Apple签署的,那么这将更难,但因为您是盲目地信任收据中的到期日期。
只要您信任您的用户是诚实的,您的方法就可以。