gpt4 book ai didi

ios - iOS8 中的应用内购买收据验证

转载 作者:塔克拉玛干 更新时间:2023-11-02 07:59:32 25 4
gpt4 key购买 nike

我们正在关注 http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation 中的 raywenderlinch 教程

但是在我们的项目中导入 verificationController 类时出现了一些错误。现在,我正在使用 iOS8。

Error is: Implicit declaration of function 'checkReciptSecurity' is invalid in C99

我也在苹果开发者网站上搜索验证类的示例代码,没有找到他们的页面。

请给我您的解决方案或提供更新到 iOS8 的验证类的链接。

最佳答案

在 VerificationController.h 中放置这样的函数原型(prototype):

- (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

// This method should be called once a transaction gets to the SKPaymentTransactionStatePurchased or SKPaymentTransactionStateRestored state
// Call it with the SKPaymentTransaction.transactionReceipt
- (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:(VerifyCompletionHandler)completionHandler
{
BOOL isOk = [self isTransactionAndItsReceiptValid:transaction];
if (!isOk)
{
// There was something wrong with the transaction we got back, so no need to call verifyReceipt.
NSLog(@"Invalid transacion");
completionHandler(FALSE);
return;
}

// The transaction looks ok, so start the verify process.

// Encode the receiptData for the itms receipt verification POST request.
NSString *jsonObjectString = [self encodeBase64:(uint8_t *)transaction.transactionReceipt.bytes
length:transaction.transactionReceipt.length];

// Create the POST request payload.
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
// Use ITMS_SANDBOX_VERIFY_RECEIPT_URL while testing against the sandbox.
NSString *serverURL = ITMS_SANDBOX_VERIFY_RECEIPT_URL; //ITMS_PROD_VERIFY_RECEIPT_URL;

// Create the POST request to the server.
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];

// The transation receipt has not been validated yet. That is done from the NSURLConnection callback.
}

// Check the validity of the receipt. If it checks out then also ensure the transaction is something
// we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation.
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
{
// Transaction is not valid.
return NO;
}

// Pull the purchase-info out of the transaction receipt, decode it, and save it for later so
// it can be cross checked with the verifyReceipt.
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"];

// Convert the string into a date
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])
{
// We've seen this transaction before.
// Had [transactionsReceiptStorageDictionary objectForKey:transactionId]
// Got purchaseInfoDict
return NO;
}

// Check the authenticity of the receipt response/signature etc.

BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
(__bridge CFDateRef)(purchaseDate));

if (!result)
{
return NO;
}

// Ensure the transaction itself is legit
if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
{
return NO;
}

// Make a note of the fact that we've seen the transaction id already
[self saveTransactionId:transactionId];

// Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary.
[transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];

return YES;
}

// Make sure the transaction details actually match the purchase info
- (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++;
}

// Optionally check the bid and bvrs match this app's current bundle ID and bundle version.
// Optionally check the requestData.
// Optionally check the dates.

if (failCount != 0)
{
return NO;
}

// The transaction and its signed content seem ok.
return YES;
}



- (BOOL)isTransactionIdUnique:(NSString *)transactionId
{
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
// Save the transactionId to the standardUserDefaults so we can check against that later
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;
}
// The transaction already exists in the defaults.
return NO;
}


- (void)saveTransactionId:(NSString *)transactionId
{
// Save the transactionId to the standardUserDefaults so we can check against that later
// If dictionary exists already then retrieve it and add new transactionID
// Regardless save transactionID to dictionary which gets saved to NSUserDefaults
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
{
// Convert the responseString into a dictionary and pull out the receipt data.
NSDictionary *verifiedReceiptDictionary = [self dictionaryFromJSONData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];

// Check the status of the verifyReceipt call
id status = [verifiedReceiptDictionary objectForKey:@"status"];
if (!status)
{
return NO;
}
int verifyReceiptStatus = [status integerValue];
// 21006 = This receipt is valid but the subscription has expired.
if (0 != verifyReceiptStatus && 21006 != verifyReceiptStatus)
{
return NO;
}

// The receipt is valid, so checked the receipt specifics now.

NSDictionary *verifiedReceiptReceiptDictionary = [verifiedReceiptDictionary objectForKey:@"receipt"];
NSString *verifiedReceiptUniqueIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_identifier"];
NSString *transactionIdFromVerifiedReceipt = [verifiedReceiptReceiptDictionary objectForKey:@"transaction_id"];

// Get the transaction's receipt data from the transactionsReceiptStorageDictionary
NSDictionary *purchaseInfoFromTransaction = [transactionsReceiptStorageDictionary objectForKey:transactionIdFromVerifiedReceipt];

if (!purchaseInfoFromTransaction)
{
// We didn't find a receipt for this transaction.
return NO;
}


// NOTE: Instead of counting errors you could just return early.
int failCount = 0;

// Verify all the receipt specifics to ensure everything matches up as expected
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")]) // iOS 6?
{
#if IS_IOS6_AWARE
// iOS 6 (or later)
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])
{
// Comment this line out to test in the Simulator.
failCount++;
}
}
#endif
} else {
// Pre iOS 6
// NSString *localIdentifier = [UIDevice currentDevice].uniqueIdentifier;
// NSString *purchaseInfoUniqueId = [purchaseInfoFromTransaction objectForKey:@"unique-identifier"];
// if (![purchaseInfoUniqueId isEqualToString:verifiedReceiptUniqueIdentifier]
// || ![purchaseInfoUniqueId isEqualToString:localIdentifier])
// {
// // Comment this line out to test in the Simulator.
// failCount++;
// }
}


// Do addition time checks for the transaction and receipt.

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)
{
//Validation suceeded. Unlock content here.
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];
}
}

// NOTE: These are needed for 4.x (as willSendRequestForAuthenticationChallenge: is not supported)
- (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
{

// Include some Security framework SPIs
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;

/*
Parse inputs:
purchase_info_string and signature_string are base64 encoded JSON blobs that need to
be decoded.
*/

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);

/*
Binary format looks as follows:

RECEIPTVERSION | SIGNATURE | CERTIFICATE SIZE | CERTIFICATE
1 byte 128 4 bytes
big endian

Extract version, signature and certificate(s).
Check receipt version == 2.
Sanity check that signature is 128 bytes.
Sanity check certificate size <= remaining payload data.
*/

#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;

/*
Make sure the signature blob is long enough to safely extract the version and
cert_len fields, then perform a sanity check on the fields.
*/
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);

/*
Validate certificate chains back to valid receipt signer; policy approximation for now
set intermediate as a trust anchor; current intermediate lapses in 2016.
*/

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);

/*
Chain is valid, use leaf key to verify signature on receipt by
calculating SHA1(version|purchaseInfo)
*/

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);

/*
Optional: Verify that the receipt certificate has the 1.2.840.113635.100.6.5.1 Null OID

The signature is a 1024-bit RSA signature.
*/

valid = YES;

outLabel:
if (leaf) CFRelease(leaf);
if (intermediate) CFRelease(intermediate);
if (trust) CFRelease(trust);
if (policy) CFRelease(policy);

return valid;
}

关于ios - iOS8 中的应用内购买收据验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26890349/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com