在VerificationController.h中,将函数原型放置如下:
- (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:(VerifyCompletionHandler)completionHandler;
BOOL checkReceiptSecurity(NSString *purchase_info_string, NSString *signature_string, CFDateRef purchaseDate);
这样做的原因是调用函数 checkReceiptSecurity 的行号在函数声明之前。
你需要修改 VerificationController.m 文件的代码。
我已经将修改后的代码放在这里。
#import "VerificationController.h"
#import "NSData+Base64.h"
static VerificationController *singleton;
@implementation VerificationController {
NSMutableDictionary * _completionHandlers;
}
+ (VerificationController *)sharedInstance
{
if (singleton == nil)
{
singleton = [[VerificationController alloc] init];
}
return singleton;
}
- (id)init
{
self = [super init];
if (self != nil)
{
transactionsReceiptStorageDictionary = [[NSMutableDictionary alloc] init];
_completionHandlers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSDictionary *)dictionaryFromPlistData:(NSData *)data
{
NSError *error;
NSDictionary *dictionaryParsed = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:nil
error:&error];
if (!dictionaryParsed)
{
if (error)
{
NSLog(@"Error parsing plist");
}
return nil;
}
return dictionaryParsed;
}
- (NSDictionary *)dictionaryFromJSONData:(NSData *)data
{
NSError *error;
NSDictionary *dictionaryParsed = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if (!dictionaryParsed)
{
if (error)
{
NSLog(@"Error parsing dictionary");
}
return nil;
}
return dictionaryParsed;
}
#pragma mark Receipt Verification
- (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:(VerifyCompletionHandler)completionHandler
{
BOOL isOk = [self isTransactionAndItsReceiptValid:transaction];
if (!isOk)
{
NSLog(@"Invalid transacion");
completionHandler(FALSE);
return;
}
NSString *jsonObjectString = [self encodeBase64:(uint8_t *)transaction.transactionReceipt.bytes
length:transaction.transactionReceipt.length];
NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\", \"password\" : \"%@\"}",
jsonObjectString, ITC_CONTENT_PROVIDER_SHARED_SECRET];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
#warning Check for the correct itms verify receipt URL
NSString *serverURL = ITMS_SANDBOX_VERIFY_RECEIPT_URL;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverURL]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:payloadData];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
_completionHandlers[[NSValue valueWithNonretainedObject:conn]] = completionHandler;
[conn start];
}
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
{
return NO;
}
NSDictionary *receiptDict = [self dictionaryFromPlistData:transaction.transactionReceipt];
NSString *transactionPurchaseInfo = [receiptDict objectForKey:@"purchase-info"];
NSString *decodedPurchaseInfo = [self decodeBase64:transactionPurchaseInfo length:nil];
NSDictionary *purchaseInfoDict = [self dictionaryFromPlistData:[decodedPurchaseInfo dataUsingEncoding:NSUTF8StringEncoding]];
NSString *transactionId = [purchaseInfoDict objectForKey:@"transaction-id"];
NSString *purchaseDateString = [purchaseInfoDict objectForKey:@"purchase-date"];
NSString *signature = [receiptDict objectForKey:@"signature"];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString stringByReplacingOccurrencesOfString:@"Etc/" withString:@""]];
if (![self isTransactionIdUnique:transactionId])
{
return NO;
}
BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
(__bridge CFDateRef)(purchaseDate));
if (!result)
{
return NO;
}
if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
{
return NO;
}
[self saveTransactionId:transactionId];
[transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];
return YES;
}
- (BOOL)doTransactionDetailsMatchPurchaseInfo:(SKPaymentTransaction *)transaction withPurchaseInfo:(NSDictionary *)purchaseInfoDict
{
if (!transaction || !purchaseInfoDict)
{
return NO;
}
int failCount = 0;
if (![transaction.payment.productIdentifier isEqualToString:[purchaseInfoDict objectForKey:@"product-id"]])
{
failCount++;
}
if (transaction.payment.quantity != [[purchaseInfoDict objectForKey:@"quantity"] intValue])
{
failCount++;
}
if (![transaction.transactionIdentifier isEqualToString:[purchaseInfoDict objectForKey:@"transaction-id"]])
{
failCount++;
}
if (failCount != 0)
{
return NO;
}
return YES;
}
- (BOOL)isTransactionIdUnique:(NSString *)transactionId
{
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize];
if (![defaults objectForKey:transactionDictionary])
{
[defaults setObject:[[NSMutableDictionary alloc] init] forKey:transactionDictionary];
[defaults synchronize];
}
if (![[defaults objectForKey:transactionDictionary] objectForKey:transactionId])
{
return YES;
}
return NO;
}
- (void)saveTransactionId:(NSString *)transactionId
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:
[defaults objectForKey:transactionDictionary]];
if (!dictionary)
{
dictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:1], transactionId, nil];
} else {
[dictionary setObject:[NSNumber numberWithInt:1] forKey:transactionId];
}
[defaults setObject:dictionary forKey:transactionDictionary];
[defaults synchronize];
}
- (BOOL)doesTransactionInfoMatchReceipt:(NSString*) receiptString
{
NSDictionary *verifiedReceiptDictionary = [self dictionaryFromJSONData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];
id status = [verifiedReceiptDictionary objectForKey:@"status"];
if (!status)
{
return NO;
}
int verifyReceiptStatus = [status integerValue];
if (0 != verifyReceiptStatus && 21006 != verifyReceiptStatus)
{
return NO;
}
NSDictionary *verifiedReceiptReceiptDictionary = [verifiedReceiptDictionary objectForKey:@"receipt"];
NSString *verifiedReceiptUniqueIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_identifier"];
NSString *transactionIdFromVerifiedReceipt = [verifiedReceiptReceiptDictionary objectForKey:@"transaction_id"];
NSDictionary *purchaseInfoFromTransaction = [transactionsReceiptStorageDictionary objectForKey:transactionIdFromVerifiedReceipt];
if (!purchaseInfoFromTransaction)
{
return NO;
}
int failCount = 0;
if (![[verifiedReceiptReceiptDictionary objectForKey:@"bid"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:@"bid"]])
{
failCount++;
}
if (![[verifiedReceiptReceiptDictionary objectForKey:@"product_id"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:@"product-id"]])
{
failCount++;
}
if (![[verifiedReceiptReceiptDictionary objectForKey:@"quantity"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:@"quantity"]])
{
failCount++;
}
if (![[verifiedReceiptReceiptDictionary objectForKey:@"item_id"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:@"item-id"]])
{
failCount++;
}
if ([[UIDevice currentDevice] respondsToSelector:NSSelectorFromString(@"identifierForVendor")])
{
#if IS_IOS6_AWARE
NSString *localIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSString *purchaseInfoUniqueVendorId = [purchaseInfoFromTransaction objectForKey:@"unique-vendor-identifier"];
NSString *verifiedReceiptVendorIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_vendor_identifier"];
if(verifiedReceiptVendorIdentifier)
{
if (![purchaseInfoUniqueVendorId isEqualToString:verifiedReceiptVendorIdentifier]
|| ![purchaseInfoUniqueVendorId isEqualToString:localIdentifier])
{
failCount++;
}
}
#endif
} else {
}
if(failCount != 0)
{
return NO;
}
return YES;
}
#pragma mark NSURLConnectionDelegate (for the verifyReceipt connection)
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"Connection failure: %@", error);
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
completionHandler(FALSE);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
if (isOk)
{
NSLog(@"Validation successful");
completionHandler(TRUE);
} else {
NSLog(@"Validation failed");
completionHandler(FALSE);
}
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
NSError *error = nil;
BOOL didUseCredential = NO;
BOOL isTrusted = [self validateTrust:trust error:&error];
if (isTrusted)
{
NSURLCredential *trust_credential = [NSURLCredential credentialForTrust:trust];
if (trust_credential)
{
[[challenge sender] useCredential:trust_credential forAuthenticationChallenge:challenge];
didUseCredential = YES;
}
}
if (!didUseCredential)
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
NSError *error = nil;
BOOL didUseCredential = NO;
BOOL isTrusted = [self validateTrust:trust error:&error];
if (isTrusted)
{
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
if (credential)
{
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
didUseCredential = YES;
}
}
if (! didUseCredential) {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
#pragma mark
#pragma mark NSURLConnection - Trust validation
- (BOOL)validateTrust:(SecTrustRef)trust error:(NSError **)error
{
extern CFStringRef kSecTrustInfoExtendedValidationKey;
extern CFDictionaryRef SecTrustCopyInfo(SecTrustRef trust);
BOOL trusted = NO;
SecTrustResultType trust_result;
if ((noErr == SecTrustEvaluate(trust, &trust_result)) && (trust_result == kSecTrustResultUnspecified))
{
NSDictionary *trust_info = (__bridge_transfer NSDictionary *)SecTrustCopyInfo(trust);
id hasEV = [trust_info objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
trusted = [hasEV isKindOfClass:[NSValue class]] && [hasEV boolValue];
}
if (trust)
{
if (!trusted && error)
{
*error = [NSError errorWithDomain:@"kSecTrustError" code:(NSInteger)trust_result userInfo:nil];
}
return trusted;
}
return NO;
}
#pragma mark
#pragma mark Base 64 encoding
- (NSString *)encodeBase64:(const uint8_t *)input length:(NSInteger)length
{
NSData * data = [NSData dataWithBytes:input length:length];
return [data base64EncodedString];
}
- (NSString *)decodeBase64:(NSString *)input length:(NSInteger *)length
{
NSData * data = [NSData dataFromBase64String:input];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
char* base64_encode(const void* buf, size_t size) {
size_t outputLength;
return NewBase64Encode(buf, size, NO, &outputLength);
}
void * base64_decode(const char* s, size_t * data_len)
{
return NewBase64Decode(s, strlen(s), data_len);
}
@end
#pragma mark
#pragma mark Check Receipt signature
#include <CommonCrypto/CommonDigest.h>
#include <Security/Security.h>
#include <AssertMacros.h>
unsigned int iTS_intermediate_der_len = 1039;
unsigned char iTS_intermediate_der[] = {
put the hexacode here
};
BOOL checkReceiptSecurity(NSString *purchase_info_string, NSString *signature_string, CFDateRef purchaseDate)
{
BOOL valid = NO;
SecCertificateRef leaf = NULL, intermediate = NULL;
SecTrustRef trust = NULL;
SecPolicyRef policy = SecPolicyCreateBasicX509();
NSData *certificate_data;
NSArray *anchors;
require([purchase_info_string canBeConvertedToEncoding:NSASCIIStringEncoding] &&
[signature_string canBeConvertedToEncoding:NSASCIIStringEncoding], outLabel);
size_t purchase_info_length;
uint8_t *purchase_info_bytes = base64_decode([purchase_info_string cStringUsingEncoding:NSASCIIStringEncoding],
&purchase_info_length);
size_t signature_length;
uint8_t *signature_bytes = base64_decode([signature_string cStringUsingEncoding:NSASCIIStringEncoding],
&signature_length);
require(purchase_info_bytes && signature_bytes, outLabel);
#pragma pack(push, 1)
struct signature_blob {
uint8_t version;
uint8_t signature[128];
uint32_t cert_len;
uint8_t certificate[];
} *signature_blob_ptr = (struct signature_blob *)signature_bytes;
#pragma pack(pop)
uint32_t certificate_len;
require(signature_length > offsetof(struct signature_blob, certificate), outLabel);
require(signature_blob_ptr->version == 2, outLabel);
certificate_len = ntohl(signature_blob_ptr->cert_len);
require(signature_length - offsetof(struct signature_blob, certificate) >= certificate_len, outLabel);
certificate_data = [NSData dataWithBytes:signature_blob_ptr->certificate length:certificate_len];
require(leaf = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificate_data), outLabel);
certificate_data = [NSData dataWithBytes:iTS_intermediate_der length:iTS_intermediate_der_len];
require(intermediate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificate_data), outLabel);
anchors = [NSArray arrayWithObject:(__bridge id)intermediate];
require(anchors, outLabel);
require_noerr(SecTrustCreateWithCertificates(leaf, policy, &trust), outLabel);
require_noerr(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef) anchors), outLabel);
if (purchaseDate)
{
require_noerr(SecTrustSetVerifyDate(trust, purchaseDate), outLabel);
}
SecTrustResultType trust_result;
require_noerr(SecTrustEvaluate(trust, &trust_result), outLabel);
require(trust_result == kSecTrustResultUnspecified, outLabel);
require(2 == SecTrustGetCertificateCount(trust), outLabel);
CC_SHA1_CTX sha1_ctx;
uint8_t to_be_verified_data[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Init(&sha1_ctx);
CC_SHA1_Update(&sha1_ctx, &signature_blob_ptr->version, sizeof(signature_blob_ptr->version));
CC_SHA1_Update(&sha1_ctx, purchase_info_bytes, purchase_info_length);
CC_SHA1_Final(to_be_verified_data, &sha1_ctx);
SecKeyRef receipt_signing_key = SecTrustCopyPublicKey(trust);
require(receipt_signing_key, outLabel);
require_noerr(SecKeyRawVerify(receipt_signing_key, kSecPaddingPKCS1SHA1,
to_be_verified_data, sizeof(to_be_verified_data),
signature_blob_ptr->signature, sizeof(signature_blob_ptr->signature)),
outLabel);
valid = YES;
outLabel:
if (leaf) CFRelease(leaf);
if (intermediate) CFRelease(intermediate);
if (trust) CFRelease(trust);
if (policy) CFRelease(policy);
return valid;
}