यहां बताया गया है कि मैंने अपनी इन-ऐप खरीदारी लाइब्रेरी RMStore में इसे कैसे हल किया । मैं समझाऊंगा कि कैसे एक लेन-देन को सत्यापित किया जाए, जिसमें पूरी रसीद का सत्यापन करना शामिल है।
एक नजर में
रसीद प्राप्त करें और लेनदेन को सत्यापित करें। यदि यह विफल रहता है, तो रसीद को ताज़ा करें और फिर से प्रयास करें। यह सत्यापन प्रक्रिया को अतुल्यकालिक बनाता है क्योंकि रसीद को ताज़ा करना अतुल्यकालिक है।
से RMStoreAppReceiptVerifier :
RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below.
if (verified) return;
// Apple recommends to refresh the receipt if validation fails on iOS
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
[self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock];
} failure:^(NSError *error) {
[self failWithBlock:failureBlock error:error];
}];
रसीद डेटा प्राप्त करना
रसीद में है [[NSBundle mainBundle] appStoreReceiptURL]
और वास्तव में एक PCKS7 कंटेनर है। मैं क्रिप्टोग्राफी में चूसता हूं इसलिए मैंने इस कंटेनर को खोलने के लिए ओपनएसएसएल का उपयोग किया। अन्य लोगों ने स्पष्ट रूप से इसे सिस्टम फ्रेमवर्क के साथ किया है ।
अपने प्रोजेक्ट में OpenSSL जोड़ना तुच्छ नहीं है। RMStore विकी मदद करनी चाहिए।
यदि आप PKCS7 कंटेनर खोलने के लिए OpenSSL का उपयोग करने का विकल्प चुनते हैं, तो आपका कोड इस तरह दिख सकता है। से RMAppReceipt :
+ (NSData*)dataFromPKCS7Path:(NSString*)path
{
const char *cpath = [[path stringByStandardizingPath] fileSystemRepresentation];
FILE *fp = fopen(cpath, "rb");
if (!fp) return nil;
PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
fclose(fp);
if (!p7) return nil;
NSData *data;
NSURL *certificateURL = [[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"];
NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL];
if ([self verifyPKCS7:p7 withCertificateData:certificateData])
{
struct pkcs7_st *contents = p7->d.sign->contents;
if (PKCS7_type_is_data(contents))
{
ASN1_OCTET_STRING *octets = contents->d.data;
data = [NSData dataWithBytes:octets->data length:octets->length];
}
}
PKCS7_free(p7);
return data;
}
हम सत्यापन के विवरण में बाद में आएंगे।
रसीद फ़ील्ड प्राप्त करना
रसीद ASN1 प्रारूप में व्यक्त की गई है। इसमें सामान्य जानकारी शामिल है, सत्यापन के उद्देश्यों के लिए कुछ क्षेत्र (हम बाद में आएंगे) और प्रत्येक लागू-में-खरीद की विशिष्ट जानकारी।
जब ASN1 पढ़ने की बात आती है तो ओपनएसएसएल बचाव में आता है। से RMAppReceipt , कुछ सहायक तरीकों का उपयोग कर:
NSMutableArray *purchases = [NSMutableArray array];
[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
const uint8_t *s = data.bytes;
const NSUInteger length = data.length;
switch (type)
{
case RMAppReceiptASN1TypeBundleIdentifier:
_bundleIdentifierData = data;
_bundleIdentifier = RMASN1ReadUTF8String(&s, length);
break;
case RMAppReceiptASN1TypeAppVersion:
_appVersion = RMASN1ReadUTF8String(&s, length);
break;
case RMAppReceiptASN1TypeOpaqueValue:
_opaqueValue = data;
break;
case RMAppReceiptASN1TypeHash:
_hash = data;
break;
case RMAppReceiptASN1TypeInAppPurchaseReceipt:
{
RMAppReceiptIAP *purchase = [[RMAppReceiptIAP alloc] initWithASN1Data:data];
[purchases addObject:purchase];
break;
}
case RMAppReceiptASN1TypeOriginalAppVersion:
_originalAppVersion = RMASN1ReadUTF8String(&s, length);
break;
case RMAppReceiptASN1TypeExpirationDate:
{
NSString *string = RMASN1ReadIA5SString(&s, length);
_expirationDate = [RMAppReceipt formatRFC3339String:string];
break;
}
}
}];
_inAppPurchases = purchases;
में app खरीद हो रही है
प्रत्येक इन-ऐप खरीदारी ASN1 में भी है। इसे प्राप्त करना सामान्य रसीद की जानकारी को पार्स करने के समान है।
से RMAppReceipt , एक ही सहायक तरीकों का उपयोग कर:
[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
const uint8_t *p = data.bytes;
const NSUInteger length = data.length;
switch (type)
{
case RMAppReceiptASN1TypeQuantity:
_quantity = RMASN1ReadInteger(&p, length);
break;
case RMAppReceiptASN1TypeProductIdentifier:
_productIdentifier = RMASN1ReadUTF8String(&p, length);
break;
case RMAppReceiptASN1TypeTransactionIdentifier:
_transactionIdentifier = RMASN1ReadUTF8String(&p, length);
break;
case RMAppReceiptASN1TypePurchaseDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_purchaseDate = [RMAppReceipt formatRFC3339String:string];
break;
}
case RMAppReceiptASN1TypeOriginalTransactionIdentifier:
_originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length);
break;
case RMAppReceiptASN1TypeOriginalPurchaseDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_originalPurchaseDate = [RMAppReceipt formatRFC3339String:string];
break;
}
case RMAppReceiptASN1TypeSubscriptionExpirationDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string];
break;
}
case RMAppReceiptASN1TypeWebOrderLineItemID:
_webOrderLineItemID = RMASN1ReadInteger(&p, length);
break;
case RMAppReceiptASN1TypeCancellationDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_cancellationDate = [RMAppReceipt formatRFC3339String:string];
break;
}
}
}];
यह ध्यान दिया जाना चाहिए कि कुछ इन-ऐप खरीदारी, जैसे उपभोग्य और गैर-नवीकरणीय सदस्यता, केवल एक बार रसीद में दिखाई देंगे। आपको खरीद के बाद इन अधिकारों को सत्यापित करना चाहिए (फिर से, RMStore इसमें आपकी मदद करता है)।
एक नज़र में सत्यापन
अब हमें रसीद और उसके सभी इन-ऐप खरीदारी से सभी क्षेत्र मिल गए। पहले हम स्वयं रसीद सत्यापित करते हैं, और फिर हम जांचते हैं कि रसीद में लेन-देन का उत्पाद है या नहीं।
नीचे वह विधि है जिसे हमने शुरुआत में वापस बुलाया था। से RMStoreAppReceiptVerificator :
- (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction
inReceipt:(RMAppReceipt*)receipt
success:(void (^)())successBlock
failure:(void (^)(NSError *error))failureBlock
{
const BOOL receiptVerified = [self verifyAppReceipt:receipt];
if (!receiptVerified)
{
[self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt failed verification", @"")];
return NO;
}
SKPayment *payment = transaction.payment;
const BOOL transactionVerified = [receipt containsInAppPurchaseOfProductIdentifier:payment.productIdentifier];
if (!transactionVerified)
{
[self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt doest not contain the given product", @"")];
return NO;
}
if (successBlock)
{
successBlock();
}
return YES;
}
रसीद का सत्यापन करना
रसीद को सत्यापित करना स्वयं को उबलता है:
- जाँच कर रहा है कि रसीद PKCS7 और ASN1 है। यह हमने पहले ही किया है।
- सत्यापित किया जा रहा है कि रसीद Apple द्वारा हस्ताक्षरित है। रसीद को पार्स करने से पहले यह किया गया था और नीचे विस्तृत होगा।
- जाँच करना कि रसीद में शामिल बंडल पहचानकर्ता आपके बंडल पहचानकर्ता से मेल खाता है। आपको अपने बंडल पहचानकर्ता को हार्डकोड करना चाहिए, क्योंकि यह आपके ऐप बंडल को संशोधित करने और किसी अन्य रसीद का उपयोग करने के लिए बहुत मुश्किल नहीं लगता है।
- जाँच रहा है कि रसीद में शामिल ऐप संस्करण आपके ऐप संस्करण पहचानकर्ता से मेल खाता है। ऊपर बताए गए समान कारणों से आपको ऐप संस्करण को हार्डकोड करना चाहिए।
- रसीद की जांच करें कि रसीद वर्तमान डिवाइस के अनुरूप है या नहीं।
RMStoreAppReceiptVerificator से उच्च-स्तर पर कोड में 5 चरण :
- (BOOL)verifyAppReceipt:(RMAppReceipt*)receipt
{
// Steps 1 & 2 were done while parsing the receipt
if (!receipt) return NO;
// Step 3
if (![receipt.bundleIdentifier isEqualToString:self.bundleIdentifier]) return NO;
// Step 4
if (![receipt.appVersion isEqualToString:self.bundleVersion]) return NO;
// Step 5
if (![receipt verifyReceiptHash]) return NO;
return YES;
}
चलो चरण 2 और 5 में ड्रिल-डाउन करें।
रसीद हस्ताक्षर का सत्यापन
जब हमने रसीद हस्ताक्षर सत्यापन पर नज़र डाली तो हमने डेटा निकाला। रसीद पर ऐप्पल इंक रूट सर्टिफिकेट के साथ हस्ताक्षर किए गए हैं, जिसे ऐप्पल रूट सर्टिफिकेट अथॉरिटी से डाउनलोड किया जा सकता है । निम्न कोड PKCS7 कंटेनर और रूट प्रमाणपत्र को डेटा के रूप में लेता है और जांचता है कि क्या वे मेल खाते हैं:
+ (BOOL)verifyPKCS7:(PKCS7*)container withCertificateData:(NSData*)certificateData
{ // Based on: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17
static int verified = 1;
int result = 0;
OpenSSL_add_all_digests(); // Required for PKCS7_verify to work
X509_STORE *store = X509_STORE_new();
if (store)
{
const uint8_t *certificateBytes = (uint8_t *)(certificateData.bytes);
X509 *certificate = d2i_X509(NULL, &certificateBytes, (long)certificateData.length);
if (certificate)
{
X509_STORE_add_cert(store, certificate);
BIO *payload = BIO_new(BIO_s_mem());
result = PKCS7_verify(container, NULL, store, NULL, payload, 0);
BIO_free(payload);
X509_free(certificate);
}
}
X509_STORE_free(store);
EVP_cleanup(); // Balances OpenSSL_add_all_digests (), per http://www.openssl.org/docs/crypto/OpenSSL_add_all_algorithms.html
return result == verified;
}
रसीद के पार्स होने से पहले, यह शुरुआत में वापस किया गया था।
रसीद हैश का सत्यापन करना
रसीद में शामिल हैश डिवाइस आईडी का SHA1 है, रसीद और बंडल आईडी में शामिल कुछ अपारदर्शी मूल्य।
यह है कि आप iOS पर रसीद हैश का सत्यापन कैसे करेंगे। से RMAppReceipt :
- (BOOL)verifyReceiptHash
{
// TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
NSUUID *uuid = [[UIDevice currentDevice] identifierForVendor];
unsigned char uuidBytes[16];
[uuid getUUIDBytes:uuidBytes];
// Order taken from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
NSMutableData *data = [NSMutableData data];
[data appendBytes:uuidBytes length:sizeof(uuidBytes)];
[data appendData:self.opaqueValue];
[data appendData:self.bundleIdentifierData];
NSMutableData *expectedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
SHA1(data.bytes, data.length, expectedHash.mutableBytes);
return [expectedHash isEqualToData:self.hash];
}
और यही इसका सार है। मुझे यहाँ या वहाँ कुछ याद आ रहा है, इसलिए मैं बाद में इस पद पर वापस आ सकता हूं। किसी भी स्थिति में, मैं अधिक विवरण के लिए पूरा कोड ब्राउज़ करने की सलाह देता हूं।