আমি আমার অ্যাপ্লিকেশন কেনার লাইব্রেরি আরএমএসটোরে এটি কীভাবে সমাধান করেছি তার একটি পদচারণা এখানে । আমি কীভাবে কোনও লেনদেন যাচাই করতে হবে তা ব্যাখ্যা করব, যার মধ্যে পুরো প্রাপ্তি যাচাই করা রয়েছে।
এক পলকে
রসিদটি পান এবং লেনদেন যাচাই করুন। যদি এটি ব্যর্থ হয়, রসিদটি রিফ্রেশ করুন এবং আবার চেষ্টা করুন। প্রাপ্তি রিফ্রেশ হিসাবে এটি যাচাইকরণ প্রক্রিয়াটিকে অবিচ্ছিন্ন করে তোলে makes
আরএমএসস্টোর অ্যাপ রিসিপটিভিয়ার থেকে :
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]
পিসিএসএস container ধারক হিসাবে রয়েছে। আমি ক্রিপ্টোগ্রাফিটি স্তন্যপান করি তাই আমি এই ধারকটি খোলার জন্য ওপেনএসএসএল ব্যবহার করেছি। অন্যরা দৃশ্যত এটি সিস্টেম ফ্রেমওয়ার্কের সাথে খাঁটিভাবে সম্পাদন করেছেন ।
আপনার প্রকল্পে ওপেনএসএসএল যুক্ত করা তুচ্ছ নয়। RMStore উইকি সাহায্য করা উচিত।
আপনি যদি পিকেসিএস 7 ধারকটি খোলার জন্য ওপেনএসএসএল ব্যবহারের বিকল্প বেছে নেন তবে আপনার কোডটি এর মতো দেখতে পারে। আরএমএপ্পি রিসিপট থেকে :
+ (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 ফর্ম্যাটে প্রকাশ করা হয়। এটিতে সাধারণ তথ্য, যাচাইকরণের উদ্দেশ্যে কিছু ক্ষেত্র রয়েছে (আমরা পরে তা আসব) এবং অ্যাপ্লিকেশন ক্রয়ের প্রতিটি প্রযোজ্য নির্দিষ্ট তথ্য রয়েছে।
আবার, এএসএন 1 পড়ার ক্ষেত্রে ওপেনএসএসএল উদ্ধার করতে আসে। আরএমএএপআরসিপেট থেকে কয়েকটি সহায়ক পদ্ধতি ব্যবহার করে:
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;
অ্যাপ্লিকেশন ক্রয় করা
প্রতিটি অ্যাপ্লিকেশন ক্রয়ও ASN1 এ। এটিকে পার্স করা সাধারণ প্রাপ্তির তথ্য পার্স করার চেয়ে অনেক অনুরূপ।
একই সহায়ক সহায়ক পদ্ধতি ব্যবহার করে আরএমএপ্পেরসিপ্ট থেকে :
[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;
}
}
}];
এটি লক্ষ করা উচিত যে নির্দিষ্ট অ্যাপ্লিকেশন ক্রয়গুলি যেমন ভোক্তাযোগ্য এবং অ-পুনর্নবীকরণযোগ্য সাবস্ক্রিপশন, প্রাপ্তি একবারে উপস্থিত হবে। ক্রয়ের পরে আপনার এই অধিকারগুলি যাচাই করা উচিত (আবার, আরএমএসটিওর এটিতে আপনাকে সহায়তা করে)।
এক নজরে যাচাইকরণ
এখন আমরা প্রাপ্তি এবং এর অ্যাপ্লিকেশনগুলির সমস্ত ক্রয় থেকে সমস্ত ক্ষেত্র পেয়েছি। প্রথমে আমরা রসিদটি নিজেই যাচাই করি এবং তারপরে আমরা সহজভাবে যাচাই করেছিলাম যে রশিদে লেনদেনের পণ্য রয়েছে।
নীচে সেই পদ্ধতিটি যা আমরা শুরুতে ফিরে ডাকলাম। আরএমএসটোর অ্যাপ রিসিপটিভেরিফিকটর থেকে :
- (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;
}
প্রাপ্তি যাচাই করা হচ্ছে
রসিদ যাচাই করা নিজেই এগুলিতে সিদ্ধ হয়:
- প্রাপ্তিটি বৈধ পিকেসিএস 7 এবং এএসএন 1 আছে কিনা তা পরীক্ষা করা হচ্ছে। আমরা ইতিমধ্যে এটি স্পষ্টভাবে সম্পন্ন করেছি।
- প্রাপ্তিটি অ্যাপল স্বাক্ষর করেছে তা যাচাই করা হচ্ছে। এটি রসিদ বিশ্লেষণের আগে করা হয়েছিল এবং এটি নীচে বিস্তারিত হবে।
- প্রাপ্তিতে অন্তর্ভুক্ত বান্ডিল শনাক্তকারী আপনার বান্ডেল শনাক্তকারীর সাথে মিলেছে কিনা তা পরীক্ষা করা হচ্ছে। আপনার অ্যাপ্লিকেশন বান্ডেলটি সংশোধন করা এবং অন্য কিছু রসিদ ব্যবহার করা খুব কঠিন বলে মনে হচ্ছে না বলে আপনার নিজের বান্ডেল শনাক্তকারীকে হার্ডকোড করা উচিত।
- রসিদে অন্তর্ভুক্ত অ্যাপ্লিকেশন সংস্করণটি আপনার অ্যাপ্লিকেশন সংস্করণ শনাক্তকারীর সাথে সম্পর্কিত। উপরে উল্লিখিত একই কারণে আপনার অ্যাপ্লিকেশন সংস্করণটিকে হার্ডকোড করা উচিত।
- রসিদটি বর্তমান ডিভাইসের সাথে মিল রয়েছে কিনা তা নিশ্চিত করার জন্য রসিদ হ্যাশ পরীক্ষা করুন।
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 ধাপে into
প্রাপ্তির স্বাক্ষর যাচাই করা হচ্ছে
যখন আমরা ডেটা বের করেছিলাম তখন আমরা রসিদ স্বাক্ষর যাচাইকরণের দিকে নজর রেখেছিলাম। প্রাপ্তিটি অ্যাপল ইনক। রুট শংসাপত্রের সাথে স্বাক্ষরিত হয়েছে, যা অ্যাপল রুট শংসাপত্র কর্তৃপক্ষ থেকে ডাউনলোড করা যেতে পারে । নিম্নলিখিত কোডটি পিকেসিএস 7 কনটেইনার এবং মূল শংসাপত্রটিকে ডেটা হিসাবে গ্রহণ করে এবং তারা মেলে কিনা তা পরীক্ষা করে:
+ (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, রশিদে অন্তর্ভুক্ত কিছু অস্বচ্ছ মান এবং বান্ডেল আইডি।
এইভাবে আপনি আইওএসে প্রাপ্তি হ্যাশ যাচাই করবেন। আরএমএপ্পি রিসিপট থেকে :
- (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];
}
এবং এটি এর সংক্ষেপে। আমি এখানে বা সেখানে কিছু অনুপস্থিত হতে পারি, তাই আমি পরে এই পোস্টে ফিরে আসতে পারি। যাইহোক, আমি আরও তথ্যের জন্য সম্পূর্ণ কোড ব্রাউজ করার পরামর্শ দিচ্ছি।