请注意看似“太容易”'验证收据的方法

时间:2014-07-02 02:39:32

标签: ios in-app-purchase receipt

我认为说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;
}

1 个答案:

答案 0 :(得分:0)

收据验证的机械步骤并不难,您的实施解决了这些问题。由于您无法信任最终用户设备,因此安全地执行此操作会更加困难。

我看到的第一个问题是您将过期日期存储在NSUserDefaults中,并且只有在“now”在过期日期之后才验证收据。因此,我可以简单地将过期日期放到NSUserDefaults中,并且您永远不会使订阅到期,也不会检查过期日期是否有效。使用加密值并存储在钥匙串中会使其更安全。

其次,您将直接从设备验证收据到Apple服务器。来自Receipt Validation Programming Guide

  

使用受信任的服务器与App Store进行通信。用你自己的   服务器允许您设计您的应用程序以识别和信任您的   服务器,并确保您的服务器与应用程序连接   存储服务器。无法在它们之间建立可信连接   用户的设备和App Store直接因为您无法控制   这个连接的任何一端。

这就是说,通过直接从您的设备连接,您可以轻松地欺骗Apple服务器。如果您确认收据是由Apple签署的,那么这将更难,但因为您是盲目地信任收据中的到期日期。

只要您信任您的用户是诚实的,您的方法就可以。