Repository: rmaddy/VerifyStoreReceiptiOS
Branch: master
Commit: 2f52d460b6a5
Files: 18
Total size: 76.8 KB
Directory structure:
gitextract_qhdmevxp/
├── LICENSE
├── README.md
├── VerifyStoreReceipt/
│ ├── Images.xcassets/
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage/
│ │ └── Contents.json
│ ├── RMAppDelegate.h
│ ├── RMAppDelegate.m
│ ├── VerifyStoreReceipt-Info.plist
│ ├── VerifyStoreReceipt-Prefix.pch
│ ├── VerifyStoreReceipt.h
│ ├── VerifyStoreReceipt.m
│ ├── en.lproj/
│ │ └── InfoPlist.strings
│ └── main.m
├── VerifyStoreReceipt.h
├── VerifyStoreReceipt.m
└── VerifyStoreReceipt.xcodeproj/
├── project.pbxproj
└── project.xcworkspace/
├── contents.xcworkspacedata
└── xcuserdata/
└── rick.xcuserdatad/
├── UserInterfaceState.xcuserstate
└── WorkspaceSettings.xcsettings
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
Copyright (c) 2013, rmaddy
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# VerifyStoreReceiptiOS
Rick Maddy, Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo, yene, David Keegan, Alessandro Segala.
These files are based off of the [roddi/ValidateStoreReceipt](https://github.com/roddi/ValidateStoreReceipt) project.
For details on iOS receipt validation from Apple, see [Receipt Validation Programming Guide](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Introduction.html) (Developer membership needed).
Unfortunately this document doesn't tell you how to process this receipt in detail, quote:
The payload of the PKCS7 container is encoded using ASN.1, as described by ITU-T X.690.
This validator parses and validates the payload and the PKCS7 container itself.
Thanks to Matthew Stevens for coming up with the parser code. Thanks to Dave Carlton for polishing it a bit. Thanks to Fraser Hess for more polish and correcting my non-native English. Thanks to anlumo for the certificate checking code. Thanks to Alessandro Segala for the In-App purchasing code.
Missing from this project:
- Apple's root certificate. This may be obtained from http://www.apple.com/certificateauthority/
- Any measures to make your app cracker proof.
## Installation
If you have an app that is more or less ready for the App Store, I think you will be able figure it out. Important is that you link with the dependencies listed in VerifyStoreReceipt.m.
## Using It
This class depends on OpenSSL being statically linked into your project. Please see https://github.com/x2on/OpenSSL-for-iPhone for one approach to getting that done.
Be aware that there will be people trying to crack your app. So cover your tracks. I won't go into details but Blocks and Grand Central Dispatch seem to be good tools for that.
## Sample Project
The sample project shows one possible way to make use of this receipt verifier. Please note that the sample app requires that you use the [OpenSSL-for-iPhone](https://github.com/x2on/OpenSSL-for-iPhone) project. Put both projects in a common parent directory. There are plenty of tasks that need to be completed to make the sample code complete but it is a framework to get you started. Like the notes in the VerifyStoreReceipt.m file, the code in the sample app should not be used as-is. If everyone uses the same code, it will be easy for hackers to work around the code making the receipt checking worthless.
## License
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: VerifyStoreReceipt/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: VerifyStoreReceipt/Images.xcassets/LaunchImage.launchimage/Contents.json
================================================
{
"images" : [
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"subtype" : "retina4",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "1x"
},
{
"orientation" : "landscape",
"idiom" : "ipad",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"orientation" : "landscape",
"idiom" : "ipad",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: VerifyStoreReceipt/RMAppDelegate.h
================================================
//
// RMAppDelegate.h
// VerifyStoreReceipt
//
// Created by Rick Maddy on 11/16/13.
// Copyright (c) 2013 Rick Maddy. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface RMAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
================================================
FILE: VerifyStoreReceipt/RMAppDelegate.m
================================================
//
// RMAppDelegate.m
// VerifyStoreReceipt
//
// Created by Rick Maddy on 11/16/13.
// Copyright (c) 2013 Rick Maddy. All rights reserved.
//
#import "RMAppDelegate.h"
#import "VerifyStoreReceipt.h"
#import <StoreKit/StoreKit.h>
// Per the instructions in VerifyStoreReceipt.m:
// These must be updated with actual values
#warning -- These values should be obfuscated
const NSString * global_bundleVersion = @"1.0.2";
const NSString * global_bundleIdentifier = @"com.example.SampleApp";
@interface RMAppDelegate () <SKRequestDelegate>
@end
@implementation RMAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self beginReceiptCheck];
// Empty white screen - there is no UI in this sample app
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
- (void)beginReceiptCheck {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
// See if there is an existing receipt or not
NSString *appRecPath = [[[NSBundle mainBundle] appStoreReceiptURL] path];
if ([[NSFileManager defaultManager] fileExistsAtPath:appRecPath]) {
// There is an existing receipt, see if it is valid.
[self validateReceipt:appRecPath tryAgain:YES];
} else {
// There is no receipt, request one
[self requestNewReceipt];
}
} else {
// Nothing to do under iOS 6 or earlier. This code requires iOS 7 or later
}
}
- (void)requestNewReceipt {
// TODO - this should be updated with the proper use of "Reachability"
// Begin a request for a receipt from Apple
SKReceiptRefreshRequest *req = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
req.delegate = self;
[req start];
}
- (void)validateReceipt:(NSString *)path tryAgain:(BOOL)again {
// See if the current receipt is valid or not
BOOL valid = verifyReceiptAtPath(path);
if (valid) {
// TODO
// We have a valid receipt, clear any failures and give the user the full app
// You may also perform further checks. At this point all we know is that there is a valid receipt.
// - You may wish to get details about in-app purchases from the receipt so you can enable just the right
// set of in-app purchases in your app.
// - You may wish to check when the user purchased the app.
// See the constants in VerifyStoreReceipt.h for data you can obtain from the receipt:
// NSDictionary *info = dictionaryWithAppStoreReceipt(path);
// NSArray *iaps = obtainInAppPurchases(path);
// Note - this may be called on a background thread. Be sure to do any UI work on the main thread
/*
dispatch_async(dispatch_get_main_queue(), ^{
// Any desired UI updates
});
*/
} else {
NSLog(@"Receipt is invalid");
if (again) {
// Try one more time to get a valid receipt. This will be reached if the current receipt is stale.
[self requestNewReceipt];
} else {
// TODO
// Invalid receipt. This probably means your app was cracked. You have various options:
// 1) Cripple the app in some fashion
// 2) Terminate the app
// 3) Alert the user
// What you do here is up to you and how you wish to handle an invalid receipt
}
}
}
- (void)trackFailedAttempt {
// TODO
// This will be reached if the code has been unable to obtain a receipt. There are various, legitimate reasons
// for such problems. But it could also mean the user has a jailbroken device that is deliberately preventing
// the app from getting a valid receipt.
// You need to decide how to handle these cases. You can choose to try again later. You can choose to limit how
// many failed attempts are allowed before treating these failures the same as having an invalid receipt.
// It's all up to you on what to do here.
// Note - this may be called on a background thread. Be sure to do any UI work on the main thread
/*
dispatch_async(dispatch_get_main_queue(), ^{
// Any desired UI updates
});
*/
}
#pragma mark SKRequestDelegate methods
- (void)requestDidFinish:(SKRequest *)request {
// The request for a receipt completed
NSString *appRecPath = [[[NSBundle mainBundle] appStoreReceiptURL] path];
if ([[NSFileManager defaultManager] fileExistsAtPath:appRecPath]) {
NSLog(@"Receipt exists");
[self validateReceipt:appRecPath tryAgain:NO];
} else {
NSLog(@"Receipt request done but there is no receipt");
// This can happen if the user cancels the login screen for the store.
// If we get here it means there is no receipt and an attempt to get it failed because the user cancelled
// the login.
[self trackFailedAttempt];
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"Error tryung to request receipt: %@", error);
// Unable to get/refresh the receipt
NSString *appRecPath = [[[NSBundle mainBundle] appStoreReceiptURL] path];
if ([[NSFileManager defaultManager] fileExistsAtPath:appRecPath]) {
// There is an existing receipt but we failed to get a new one. This means the existing receipt is invalid
[self trackFailedAttempt];
} else {
// There is no receipt and we were unable to get a new one
[self trackFailedAttempt];
}
}
@end
================================================
FILE: VerifyStoreReceipt/VerifyStoreReceipt-Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.maddyhome.iphone.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
FILE: VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch
================================================
//
// Prefix header
//
// The contents of this file are implicitly included at the beginning of every source file.
//
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#endif
================================================
FILE: VerifyStoreReceipt/VerifyStoreReceipt.h
================================================
//
// VerifyStoreReceipt.h
//
// Based on validatereceipt.h
// Created by Ruotger Skupin on 23.10.10.
// Copyright 2010-2011 Matthew Stevens, Ruotger Skupin, Apple, Alessandro Segala. All rights reserved.
//
// Modified for iOS, converted to ARC, and added additional fields by Rick Maddy 2013-08-20
// Copyright 2013 Rick Maddy. All rights reserved.
//
/*
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
extern NSString *kReceiptBundleIdentifer;
extern NSString *kReceiptBundleIdentiferData;
extern NSString *kReceiptVersion;
extern NSString *kReceiptOpaqueValue;
extern NSString *kReceiptHash;
extern NSString *kReceiptInApp;
extern NSString *kReceiptOriginalVersion;
extern NSString *kReceiptExpirationDate;
extern NSString *kReceiptInAppQuantity;
extern NSString *kReceiptInAppProductIdentifier;
extern NSString *kReceiptInAppTransactionIdentifier;
extern NSString *kReceiptInAppPurchaseDate;
extern NSString *kReceiptInAppOriginalTransactionIdentifier;
extern NSString *kReceiptInAppOriginalPurchaseDate;
extern NSString *kReceiptInAppSubscriptionExpirationDate;
extern NSString *kReceiptInAppCancellationDate;
extern NSString *kReceiptInAppWebOrderLineItemID;
NSDictionary *dictionaryWithAppStoreReceipt(NSString *receiptPath);
NSArray *obtainInAppPurchases(NSString *receiptPath);
BOOL verifyReceiptAtPath(NSString *receiptPath);
================================================
FILE: VerifyStoreReceipt/VerifyStoreReceipt.m
================================================
//
// validatereceipt.m
//
// Based on validatereceipt.m
// Created by Ruotger Skupin on 23.10.10.
// Copyright 2010-2011 Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo, David Keegan, Alessandro Segala. All rights reserved.
//
// Modified for iOS, converted to ARC, and added additional fields by Rick Maddy 2013-08-20
// Copyright 2013 Rick Maddy. All rights reserved.
//
/*
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "VerifyStoreReceipt.h"
// link with Foundation.framework, Security.framework, libssl and libCrypto (via -lssl -lcrypto in Other Linker Flags)
#import <Security/Security.h>
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#ifndef YES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK
#warning --- DON'T USE THIS CODE AS IS! IF EVERYONE USES THE SAME CODE
#warning --- IT IS PRETTY EASY TO BUILD AN AUTOMATIC CRACKING TOOL
#warning --- FOR APPS USING THIS CODE!
#warning --- BY USING THIS CODE YOU ACCEPT TAKING THE RESPONSIBILITY FOR
#warning --- ANY DAMAGE!
#warning ---
#warning --- YOU HAVE BEEN WARNED!
// if you want to take that risk, add "-DYES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK" in the build settings at "Other C Flags"
#endif // YES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK
#define VRCFRelease(object) if(object) CFRelease(object)
NSString *kReceiptBundleIdentifier = @"BundleIdentifier";
NSString *kReceiptBundleIdentifierData = @"BundleIdentifierData";
NSString *kReceiptVersion = @"Version";
NSString *kReceiptOpaqueValue = @"OpaqueValue";
NSString *kReceiptHash = @"Hash";
NSString *kReceiptInApp = @"InApp";
NSString *kReceiptOriginalVersion = @"OrigVer";
NSString *kReceiptExpirationDate = @"ExpDate";
NSString *kReceiptInAppQuantity = @"Quantity";
NSString *kReceiptInAppProductIdentifier = @"ProductIdentifier";
NSString *kReceiptInAppTransactionIdentifier = @"TransactionIdentifier";
NSString *kReceiptInAppPurchaseDate = @"PurchaseDate";
NSString *kReceiptInAppOriginalTransactionIdentifier = @"OriginalTransactionIdentifier";
NSString *kReceiptInAppOriginalPurchaseDate = @"OriginalPurchaseDate";
NSString *kReceiptInAppSubscriptionExpirationDate = @"SubExpDate";
NSString *kReceiptInAppCancellationDate = @"CancelDate";
NSString *kReceiptInAppWebOrderLineItemID = @"WebItemId";
NSData *appleRootCert(void) {
// Obtain the Apple Inc. root certificate from http://www.apple.com/certificateauthority/
// Download the Apple Inc. Root Certificate ( http://www.apple.com/appleca/AppleIncRootCertificate.cer )
// Add the AppleIncRootCertificate.cer to your app's resource bundle.
NSData *cert = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"]];
return cert;
}
// ASN.1 values for In-App Purchase values
#define INAPP_ATTR_START 1700
#define INAPP_QUANTITY 1701
#define INAPP_PRODID 1702
#define INAPP_TRANSID 1703
#define INAPP_PURCHDATE 1704
#define INAPP_ORIGTRANSID 1705
#define INAPP_ORIGPURCHDATE 1706
#define INAPP_ATTR_END 1707
#define INAPP_SUBEXP_DATE 1708
#define INAPP_WEBORDER 1711
#define INAPP_CANCEL_DATE 1712
NSArray *parseInAppPurchasesData(NSData *inappData) {
int type = 0;
int xclass = 0;
long length = 0;
NSUInteger dataLenght = [inappData length];
const uint8_t *p = [inappData bytes];
const uint8_t *end = p + dataLenght;
NSMutableArray *resultArray = [NSMutableArray array];
while (p < end) {
ASN1_get_object(&p, &length, &type, &xclass, end - p);
const uint8_t *set_end = p + length;
if(type != V_ASN1_SET) {
break;
}
NSMutableDictionary *item = [[NSMutableDictionary alloc] initWithCapacity:6];
while (p < set_end) {
ASN1_get_object(&p, &length, &type, &xclass, set_end - p);
if (type != V_ASN1_SEQUENCE) {
break;
}
const uint8_t *seq_end = p + length;
int attr_type = 0;
int attr_version = 0;
// Attribute type
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER) {
if(length == 1) {
attr_type = p[0];
}
else if(length == 2) {
attr_type = p[0] * 0x100 + p[1]
;
}
}
p += length;
// Attribute version
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER && length == 1) {
// clang analyser hit (wontfix at the moment, since the code might come in handy later)
// But if someone has a convincing case throwing that out, I might do so, Roddi
attr_version = p[0];
}
p += length;
// Only parse attributes we're interested in
if ((attr_type > INAPP_ATTR_START && attr_type < INAPP_ATTR_END) || attr_type == INAPP_SUBEXP_DATE || attr_type == INAPP_WEBORDER || attr_type == INAPP_CANCEL_DATE) {
NSString *key = nil;
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_OCTET_STRING) {
//NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length];
// Integers
if (attr_type == INAPP_QUANTITY || attr_type == INAPP_WEBORDER) {
int num_type = 0;
long num_length = 0;
const uint8_t *num_p = p;
ASN1_get_object(&num_p, &num_length, &num_type, &xclass, seq_end - num_p);
if (num_type == V_ASN1_INTEGER) {
NSUInteger quantity = 0;
if (num_length) {
quantity += num_p[0];
if (num_length > 1) {
quantity += num_p[1] * 0x100;
if (num_length > 2) {
quantity += num_p[2] * 0x10000;
if (num_length > 3) {
quantity += num_p[3] * 0x1000000;
}
}
}
}
NSNumber *num = [[NSNumber alloc] initWithUnsignedInteger:quantity];
if (attr_type == INAPP_QUANTITY) {
[item setObject:num forKey:kReceiptInAppQuantity];
} else if (attr_type == INAPP_WEBORDER) {
[item setObject:num forKey:kReceiptInAppWebOrderLineItemID];
}
}
}
// Strings
if (attr_type == INAPP_PRODID ||
attr_type == INAPP_TRANSID ||
attr_type == INAPP_ORIGTRANSID ||
attr_type == INAPP_PURCHDATE ||
attr_type == INAPP_ORIGPURCHDATE ||
attr_type == INAPP_SUBEXP_DATE ||
attr_type == INAPP_CANCEL_DATE) {
int str_type = 0;
long str_length = 0;
const uint8_t *str_p = p;
ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p);
if (str_type == V_ASN1_UTF8STRING) {
switch (attr_type) {
case INAPP_PRODID:
key = kReceiptInAppProductIdentifier;
break;
case INAPP_TRANSID:
key = kReceiptInAppTransactionIdentifier;
break;
case INAPP_ORIGTRANSID:
key = kReceiptInAppOriginalTransactionIdentifier;
break;
}
if (key) {
NSString *string = [[NSString alloc] initWithBytes:str_p
length:(NSUInteger)str_length
encoding:NSUTF8StringEncoding];
[item setObject:string forKey:key];
}
}
if (str_type == V_ASN1_IA5STRING) {
switch (attr_type) {
case INAPP_PURCHDATE:
key = kReceiptInAppPurchaseDate;
break;
case INAPP_ORIGPURCHDATE:
key = kReceiptInAppOriginalPurchaseDate;
break;
case INAPP_SUBEXP_DATE:
key = kReceiptInAppSubscriptionExpirationDate;
break;
case INAPP_CANCEL_DATE:
key = kReceiptInAppCancellationDate;
break;
}
if (key) {
NSString *string = [[NSString alloc] initWithBytes:str_p
length:(NSUInteger)str_length
encoding:NSASCIIStringEncoding];
[item setObject:string forKey:key];
}
}
}
}
p += length;
}
// Skip any remaining fields in this SEQUENCE
while (p < seq_end) {
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
p += length;
}
}
// Skip any remaining fields in this SET
while (p < set_end) {
ASN1_get_object(&p, &length, &type, &xclass, set_end - p);
p += length;
}
[resultArray addObject:item];
}
return resultArray;
}
// ASN.1 values for the App Store receipt
#define ATTR_START 1
#define BUNDLE_ID 2
#define VERSION 3
#define OPAQUE_VALUE 4
#define HASH 5
#define ATTR_END 6
#define INAPP_PURCHASE 17
#define ORIG_VERSION 19
#define EXPIRE_DATE 21
NSDictionary *dictionaryWithAppStoreReceipt(NSString *receiptPath) {
NSData * rootCertData = appleRootCert();
ERR_load_PKCS7_strings();
ERR_load_X509_strings();
OpenSSL_add_all_digests();
// Expected input is a PKCS7 container with signed data containing
// an ASN.1 SET of SEQUENCE structures. Each SEQUENCE contains
// two INTEGERS and an OCTET STRING.
const char * path = [[receiptPath stringByStandardizingPath] fileSystemRepresentation];
FILE *fp = fopen(path, "rb");
if (fp == NULL) {
return nil;
}
PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
fclose(fp);
// Check if the receipt file was invalid (otherwise we go crashing and burning)
if (p7 == NULL) {
return nil;
}
if (!PKCS7_type_is_signed(p7)) {
PKCS7_free(p7);
return nil;
}
if (!PKCS7_type_is_data(p7->d.sign->contents)) {
PKCS7_free(p7);
return nil;
}
int verifyReturnValue = 0;
X509_STORE *store = X509_STORE_new();
if (store) {
const uint8_t *data = (uint8_t *)(rootCertData.bytes);
X509 *appleCA = d2i_X509(NULL, &data, (long)rootCertData.length);
if (appleCA) {
BIO *payload = BIO_new(BIO_s_mem());
X509_STORE_add_cert(store, appleCA);
if (payload) {
verifyReturnValue = PKCS7_verify(p7,NULL,store,NULL,payload,0);
BIO_free(payload);
}
X509_free(appleCA);
}
X509_STORE_free(store);
}
EVP_cleanup();
if (verifyReturnValue != 1) {
PKCS7_free(p7);
return nil;
}
ASN1_OCTET_STRING *octets = p7->d.sign->contents->d.data;
const uint8_t *p = octets->data;
const uint8_t *end = p + octets->length;
int type = 0;
int xclass = 0;
long length = 0;
ASN1_get_object(&p, &length, &type, &xclass, end - p);
if (type != V_ASN1_SET) {
PKCS7_free(p7);
return nil;
}
NSMutableDictionary *info = [NSMutableDictionary dictionary];
while (p < end) {
ASN1_get_object(&p, &length, &type, &xclass, end - p);
if (type != V_ASN1_SEQUENCE) {
break;
}
const uint8_t *seq_end = p + length;
int attr_type = 0;
int attr_version = 0;
// Attribute type
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER && length == 1) {
attr_type = p[0];
}
p += length;
// Attribute version
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER && length == 1) {
attr_version = p[0];
attr_version = attr_version;
}
p += length;
// Only parse attributes we're interested in
if ((attr_type > ATTR_START && attr_type < ATTR_END) || attr_type == INAPP_PURCHASE || attr_type == ORIG_VERSION || attr_type == EXPIRE_DATE) {
NSString *key = nil;
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_OCTET_STRING) {
NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length];
// Bytes
if (attr_type == BUNDLE_ID || attr_type == OPAQUE_VALUE || attr_type == HASH) {
switch (attr_type) {
case BUNDLE_ID:
// This is included for hash generation
key = kReceiptBundleIdentifierData;
break;
case OPAQUE_VALUE:
key = kReceiptOpaqueValue;
break;
case HASH:
key = kReceiptHash;
break;
}
if (key) {
[info setObject:data forKey:key];
}
}
// Strings
if (attr_type == BUNDLE_ID || attr_type == VERSION || attr_type == ORIG_VERSION) {
int str_type = 0;
long str_length = 0;
const uint8_t *str_p = p;
ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p);
if (str_type == V_ASN1_UTF8STRING) {
switch (attr_type) {
case BUNDLE_ID:
key = kReceiptBundleIdentifier;
break;
case VERSION:
key = kReceiptVersion;
break;
case ORIG_VERSION:
key = kReceiptOriginalVersion;
break;
}
if (key) {
NSString *string = [[NSString alloc] initWithBytes:str_p
length:(NSUInteger)str_length
encoding:NSUTF8StringEncoding];
[info setObject:string forKey:key];
}
}
}
// In-App purchases
if (attr_type == INAPP_PURCHASE) {
NSArray *inApp = parseInAppPurchasesData(data);
NSArray *current = info[kReceiptInApp];
if (current) {
info[kReceiptInApp] = [current arrayByAddingObjectsFromArray:inApp];
} else {
[info setObject:inApp forKey:kReceiptInApp];
}
}
}
p += length;
}
// Skip any remaining fields in this SEQUENCE
while (p < seq_end) {
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
p += length;
}
}
PKCS7_free(p7);
return info;
}
NSArray *obtainInAppPurchases(NSString *receiptPath) {
// According to the documentation, we need to validate the receipt first.
// If the receipt is not valid, no In-App purchase is valid.
// This performs a "quick" validation. Please use validateReceiptAtPath to perform a full validation.
NSDictionary *receipt = dictionaryWithAppStoreReceipt(receiptPath);
if (!receipt) {
return nil;
}
NSArray *purchases = [receipt objectForKey:kReceiptInApp];
if(!purchases || ![purchases isKindOfClass:[NSArray class]]) {
return nil;
}
return purchases;
}
extern const NSString * global_bundleVersion;
extern const NSString * global_bundleIdentifier;
// in your project define those two somewhere as such:
// const NSString * global_bundleVersion = @"1.0.2";
// const NSString * global_bundleIdentifier = @"com.example.SampleApp";
BOOL verifyReceiptAtPath(NSString *receiptPath) {
// it turns out, it's a bad idea, to use these two NSBundle methods in your app:
//
// bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
// bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
//
// http://www.craftymind.com/2011/01/06/mac-app-store-hacked-how-developers-can-better-protect-themselves/
//
// so use hard coded values instead (probably even somehow obfuscated)
NSString *bundleVersion = (NSString*)global_bundleVersion;
NSString *bundleIdentifier = (NSString*)global_bundleIdentifier;
// avoid making stupid mistakes --> check again
NSCAssert([bundleVersion isEqualToString:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]],
@"whoops! check the hard-coded CFBundleVersion!");
NSCAssert([bundleIdentifier isEqualToString:[[NSBundle mainBundle] bundleIdentifier]],
@"whoops! check the hard-coded bundle identifier!");
NSDictionary *receipt = dictionaryWithAppStoreReceipt(receiptPath);
if (!receipt) {
return NO;
}
unsigned char uuidBytes[16];
NSUUID *vendorUUID = [[UIDevice currentDevice] identifierForVendor];
[vendorUUID getUUIDBytes:uuidBytes];
NSMutableData *input = [NSMutableData data];
[input appendBytes:uuidBytes length:sizeof(uuidBytes)];
[input appendData:[receipt objectForKey:kReceiptOpaqueValue]];
[input appendData:[receipt objectForKey:kReceiptBundleIdentifierData]];
NSMutableData *hash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
SHA1([input bytes], [input length], [hash mutableBytes]);
if ([bundleIdentifier isEqualToString:[receipt objectForKey:kReceiptBundleIdentifier]] &&
[bundleVersion isEqualToString:[receipt objectForKey:kReceiptVersion]] &&
[hash isEqualToData:[receipt objectForKey:kReceiptHash]]) {
return YES;
}
return NO;
}
================================================
FILE: VerifyStoreReceipt/en.lproj/InfoPlist.strings
================================================
/* Localized versions of Info.plist keys */
================================================
FILE: VerifyStoreReceipt/main.m
================================================
//
// main.m
// VerifyStoreReceipt
//
// Created by Rick Maddy on 11/16/13.
// Copyright (c) 2013 Rick Maddy. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "RMAppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([RMAppDelegate class]));
}
}
================================================
FILE: VerifyStoreReceipt.h
================================================
//
// VerifyStoreReceipt.h
//
// Based on validatereceipt.h
// Created by Ruotger Skupin on 23.10.10.
// Copyright 2010-2011 Matthew Stevens, Ruotger Skupin, Apple, Alessandro Segala. All rights reserved.
//
// Modified for iOS, converted to ARC, and added additional fields by Rick Maddy 2013-08-20
// Copyright 2013 Rick Maddy. All rights reserved.
//
/*
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
extern NSString *kReceiptBundleIdentifer;
extern NSString *kReceiptBundleIdentiferData;
extern NSString *kReceiptVersion;
extern NSString *kReceiptOpaqueValue;
extern NSString *kReceiptHash;
extern NSString *kReceiptInApp;
extern NSString *kReceiptOriginalVersion;
extern NSString *kReceiptExpirationDate;
extern NSString *kReceiptInAppQuantity;
extern NSString *kReceiptInAppProductIdentifier;
extern NSString *kReceiptInAppTransactionIdentifier;
extern NSString *kReceiptInAppPurchaseDate;
extern NSString *kReceiptInAppOriginalTransactionIdentifier;
extern NSString *kReceiptInAppOriginalPurchaseDate;
extern NSString *kReceiptInAppSubscriptionExpirationDate;
extern NSString *kReceiptInAppCancellationDate;
extern NSString *kReceiptInAppWebOrderLineItemID;
NSDictionary *dictionaryWithAppStoreReceipt(NSString *receiptPath);
NSArray *obtainInAppPurchases(NSString *receiptPath);
BOOL verifyReceiptAtPath(NSString *receiptPath);
================================================
FILE: VerifyStoreReceipt.m
================================================
//
// validatereceipt.m
//
// Based on validatereceipt.m
// Created by Ruotger Skupin on 23.10.10.
// Copyright 2010-2011 Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo, David Keegan, Alessandro Segala. All rights reserved.
//
// Modified for iOS, converted to ARC, and added additional fields by Rick Maddy 2013-08-20
// Copyright 2013 Rick Maddy. All rights reserved.
//
/*
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "VerifyStoreReceipt.h"
// link with Foundation.framework, Security.framework, libssl and libCrypto (via -lssl -lcrypto in Other Linker Flags)
#import <Security/Security.h>
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#ifndef YES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK
#warning --- DON'T USE THIS CODE AS IS! IF EVERYONE USES THE SAME CODE
#warning --- IT IS PRETTY EASY TO BUILD AN AUTOMATIC CRACKING TOOL
#warning --- FOR APPS USING THIS CODE!
#warning --- BY USING THIS CODE YOU ACCEPT TAKING THE RESPONSIBILITY FOR
#warning --- ANY DAMAGE!
#warning ---
#warning --- YOU HAVE BEEN WARNED!
// if you want to take that risk, add "-DYES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK" in the build settings at "Other C Flags"
#endif // YES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK
#define VRCFRelease(object) if(object) CFRelease(object)
NSString *kReceiptBundleIdentifier = @"BundleIdentifier";
NSString *kReceiptBundleIdentifierData = @"BundleIdentifierData";
NSString *kReceiptVersion = @"Version";
NSString *kReceiptOpaqueValue = @"OpaqueValue";
NSString *kReceiptHash = @"Hash";
NSString *kReceiptInApp = @"InApp";
NSString *kReceiptOriginalVersion = @"OrigVer";
NSString *kReceiptExpirationDate = @"ExpDate";
NSString *kReceiptInAppQuantity = @"Quantity";
NSString *kReceiptInAppProductIdentifier = @"ProductIdentifier";
NSString *kReceiptInAppTransactionIdentifier = @"TransactionIdentifier";
NSString *kReceiptInAppPurchaseDate = @"PurchaseDate";
NSString *kReceiptInAppOriginalTransactionIdentifier = @"OriginalTransactionIdentifier";
NSString *kReceiptInAppOriginalPurchaseDate = @"OriginalPurchaseDate";
NSString *kReceiptInAppSubscriptionExpirationDate = @"SubExpDate";
NSString *kReceiptInAppCancellationDate = @"CancelDate";
NSString *kReceiptInAppWebOrderLineItemID = @"WebItemId";
NSData *appleRootCert(void) {
// Obtain the Apple Inc. root certificate from http://www.apple.com/certificateauthority/
// Download the Apple Inc. Root Certificate ( http://www.apple.com/appleca/AppleIncRootCertificate.cer )
// Add the AppleIncRootCertificate.cer to your app's resource bundle.
NSData *cert = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"]];
return cert;
}
// ASN.1 values for In-App Purchase values
#define INAPP_ATTR_START 1700
#define INAPP_QUANTITY 1701
#define INAPP_PRODID 1702
#define INAPP_TRANSID 1703
#define INAPP_PURCHDATE 1704
#define INAPP_ORIGTRANSID 1705
#define INAPP_ORIGPURCHDATE 1706
#define INAPP_ATTR_END 1707
#define INAPP_SUBEXP_DATE 1708
#define INAPP_WEBORDER 1711
#define INAPP_CANCEL_DATE 1712
NSArray *parseInAppPurchasesData(NSData *inappData) {
int type = 0;
int xclass = 0;
long length = 0;
NSUInteger dataLenght = [inappData length];
const uint8_t *p = [inappData bytes];
const uint8_t *end = p + dataLenght;
NSMutableArray *resultArray = [NSMutableArray array];
while (p < end) {
ASN1_get_object(&p, &length, &type, &xclass, end - p);
const uint8_t *set_end = p + length;
if(type != V_ASN1_SET) {
break;
}
NSMutableDictionary *item = [[NSMutableDictionary alloc] initWithCapacity:6];
while (p < set_end) {
ASN1_get_object(&p, &length, &type, &xclass, set_end - p);
if (type != V_ASN1_SEQUENCE) {
break;
}
const uint8_t *seq_end = p + length;
int attr_type = 0;
int attr_version = 0;
// Attribute type
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER) {
if(length == 1) {
attr_type = p[0];
}
else if(length == 2) {
attr_type = p[0] * 0x100 + p[1]
;
}
}
p += length;
// Attribute version
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER && length == 1) {
// clang analyser hit (wontfix at the moment, since the code might come in handy later)
// But if someone has a convincing case throwing that out, I might do so, Roddi
attr_version = p[0];
}
p += length;
// Only parse attributes we're interested in
if ((attr_type > INAPP_ATTR_START && attr_type < INAPP_ATTR_END) || attr_type == INAPP_SUBEXP_DATE || attr_type == INAPP_WEBORDER || attr_type == INAPP_CANCEL_DATE) {
NSString *key = nil;
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_OCTET_STRING) {
//NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length];
// Integers
if (attr_type == INAPP_QUANTITY || attr_type == INAPP_WEBORDER) {
int num_type = 0;
long num_length = 0;
const uint8_t *num_p = p;
ASN1_get_object(&num_p, &num_length, &num_type, &xclass, seq_end - num_p);
if (num_type == V_ASN1_INTEGER) {
NSUInteger quantity = 0;
if (num_length) {
quantity += num_p[0];
if (num_length > 1) {
quantity += num_p[1] * 0x100;
if (num_length > 2) {
quantity += num_p[2] * 0x10000;
if (num_length > 3) {
quantity += num_p[3] * 0x1000000;
}
}
}
}
NSNumber *num = [[NSNumber alloc] initWithUnsignedInteger:quantity];
if (attr_type == INAPP_QUANTITY) {
[item setObject:num forKey:kReceiptInAppQuantity];
} else if (attr_type == INAPP_WEBORDER) {
[item setObject:num forKey:kReceiptInAppWebOrderLineItemID];
}
}
}
// Strings
if (attr_type == INAPP_PRODID ||
attr_type == INAPP_TRANSID ||
attr_type == INAPP_ORIGTRANSID ||
attr_type == INAPP_PURCHDATE ||
attr_type == INAPP_ORIGPURCHDATE ||
attr_type == INAPP_SUBEXP_DATE ||
attr_type == INAPP_CANCEL_DATE) {
int str_type = 0;
long str_length = 0;
const uint8_t *str_p = p;
ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p);
if (str_type == V_ASN1_UTF8STRING) {
switch (attr_type) {
case INAPP_PRODID:
key = kReceiptInAppProductIdentifier;
break;
case INAPP_TRANSID:
key = kReceiptInAppTransactionIdentifier;
break;
case INAPP_ORIGTRANSID:
key = kReceiptInAppOriginalTransactionIdentifier;
break;
}
if (key) {
NSString *string = [[NSString alloc] initWithBytes:str_p
length:(NSUInteger)str_length
encoding:NSUTF8StringEncoding];
[item setObject:string forKey:key];
}
}
if (str_type == V_ASN1_IA5STRING) {
switch (attr_type) {
case INAPP_PURCHDATE:
key = kReceiptInAppPurchaseDate;
break;
case INAPP_ORIGPURCHDATE:
key = kReceiptInAppOriginalPurchaseDate;
break;
case INAPP_SUBEXP_DATE:
key = kReceiptInAppSubscriptionExpirationDate;
break;
case INAPP_CANCEL_DATE:
key = kReceiptInAppCancellationDate;
break;
}
if (key) {
NSString *string = [[NSString alloc] initWithBytes:str_p
length:(NSUInteger)str_length
encoding:NSASCIIStringEncoding];
[item setObject:string forKey:key];
}
}
}
}
p += length;
}
// Skip any remaining fields in this SEQUENCE
while (p < seq_end) {
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
p += length;
}
}
// Skip any remaining fields in this SET
while (p < set_end) {
ASN1_get_object(&p, &length, &type, &xclass, set_end - p);
p += length;
}
[resultArray addObject:item];
}
return resultArray;
}
// ASN.1 values for the App Store receipt
#define ATTR_START 1
#define BUNDLE_ID 2
#define VERSION 3
#define OPAQUE_VALUE 4
#define HASH 5
#define ATTR_END 6
#define INAPP_PURCHASE 17
#define ORIG_VERSION 19
#define EXPIRE_DATE 21
NSDictionary *dictionaryWithAppStoreReceipt(NSString *receiptPath) {
NSData * rootCertData = appleRootCert();
ERR_load_PKCS7_strings();
ERR_load_X509_strings();
OpenSSL_add_all_digests();
// Expected input is a PKCS7 container with signed data containing
// an ASN.1 SET of SEQUENCE structures. Each SEQUENCE contains
// two INTEGERS and an OCTET STRING.
const char * path = [[receiptPath stringByStandardizingPath] fileSystemRepresentation];
FILE *fp = fopen(path, "rb");
if (fp == NULL) {
return nil;
}
PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
fclose(fp);
// Check if the receipt file was invalid (otherwise we go crashing and burning)
if (p7 == NULL) {
return nil;
}
if (!PKCS7_type_is_signed(p7)) {
PKCS7_free(p7);
return nil;
}
if (!PKCS7_type_is_data(p7->d.sign->contents)) {
PKCS7_free(p7);
return nil;
}
int verifyReturnValue = 0;
X509_STORE *store = X509_STORE_new();
if (store) {
const uint8_t *data = (uint8_t *)(rootCertData.bytes);
X509 *appleCA = d2i_X509(NULL, &data, (long)rootCertData.length);
if (appleCA) {
BIO *payload = BIO_new(BIO_s_mem());
X509_STORE_add_cert(store, appleCA);
if (payload) {
verifyReturnValue = PKCS7_verify(p7,NULL,store,NULL,payload,0);
BIO_free(payload);
}
X509_free(appleCA);
}
X509_STORE_free(store);
}
EVP_cleanup();
if (verifyReturnValue != 1) {
PKCS7_free(p7);
return nil;
}
ASN1_OCTET_STRING *octets = p7->d.sign->contents->d.data;
const uint8_t *p = octets->data;
const uint8_t *end = p + octets->length;
int type = 0;
int xclass = 0;
long length = 0;
ASN1_get_object(&p, &length, &type, &xclass, end - p);
if (type != V_ASN1_SET) {
PKCS7_free(p7);
return nil;
}
NSMutableDictionary *info = [NSMutableDictionary dictionary];
while (p < end) {
ASN1_get_object(&p, &length, &type, &xclass, end - p);
if (type != V_ASN1_SEQUENCE) {
break;
}
const uint8_t *seq_end = p + length;
int attr_type = 0;
int attr_version = 0;
// Attribute type
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER && length == 1) {
attr_type = p[0];
}
p += length;
// Attribute version
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_INTEGER && length == 1) {
attr_version = p[0];
attr_version = attr_version;
}
p += length;
// Only parse attributes we're interested in
if ((attr_type > ATTR_START && attr_type < ATTR_END) || attr_type == INAPP_PURCHASE || attr_type == ORIG_VERSION || attr_type == EXPIRE_DATE) {
NSString *key = nil;
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
if (type == V_ASN1_OCTET_STRING) {
NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length];
// Bytes
if (attr_type == BUNDLE_ID || attr_type == OPAQUE_VALUE || attr_type == HASH) {
switch (attr_type) {
case BUNDLE_ID:
// This is included for hash generation
key = kReceiptBundleIdentifierData;
break;
case OPAQUE_VALUE:
key = kReceiptOpaqueValue;
break;
case HASH:
key = kReceiptHash;
break;
}
if (key) {
[info setObject:data forKey:key];
}
}
// Strings
if (attr_type == BUNDLE_ID || attr_type == VERSION || attr_type == ORIG_VERSION) {
int str_type = 0;
long str_length = 0;
const uint8_t *str_p = p;
ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p);
if (str_type == V_ASN1_UTF8STRING) {
switch (attr_type) {
case BUNDLE_ID:
key = kReceiptBundleIdentifier;
break;
case VERSION:
key = kReceiptVersion;
break;
case ORIG_VERSION:
key = kReceiptOriginalVersion;
break;
}
if (key) {
NSString *string = [[NSString alloc] initWithBytes:str_p
length:(NSUInteger)str_length
encoding:NSUTF8StringEncoding];
[info setObject:string forKey:key];
}
}
}
// In-App purchases
if (attr_type == INAPP_PURCHASE) {
NSArray *inApp = parseInAppPurchasesData(data);
NSArray *current = info[kReceiptInApp];
if (current) {
info[kReceiptInApp] = [current arrayByAddingObjectsFromArray:inApp];
} else {
[info setObject:inApp forKey:kReceiptInApp];
}
}
}
p += length;
}
// Skip any remaining fields in this SEQUENCE
while (p < seq_end) {
ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
p += length;
}
}
PKCS7_free(p7);
return info;
}
NSArray *obtainInAppPurchases(NSString *receiptPath) {
// According to the documentation, we need to validate the receipt first.
// If the receipt is not valid, no In-App purchase is valid.
// This performs a "quick" validation. Please use validateReceiptAtPath to perform a full validation.
NSDictionary *receipt = dictionaryWithAppStoreReceipt(receiptPath);
if (!receipt) {
return nil;
}
NSArray *purchases = [receipt objectForKey:kReceiptInApp];
if(!purchases || ![purchases isKindOfClass:[NSArray class]]) {
return nil;
}
return purchases;
}
extern const NSString * global_bundleVersion;
extern const NSString * global_bundleIdentifier;
// in your project define those two somewhere as such:
// const NSString * global_bundleVersion = @"1.0.2";
// const NSString * global_bundleIdentifier = @"com.example.SampleApp";
BOOL verifyReceiptAtPath(NSString *receiptPath) {
// it turns out, it's a bad idea, to use these two NSBundle methods in your app:
//
// bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
// bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
//
// http://www.craftymind.com/2011/01/06/mac-app-store-hacked-how-developers-can-better-protect-themselves/
//
// so use hard coded values instead (probably even somehow obfuscated)
NSString *bundleVersion = (NSString*)global_bundleVersion;
NSString *bundleIdentifier = (NSString*)global_bundleIdentifier;
// avoid making stupid mistakes --> check again
NSCAssert([bundleVersion isEqualToString:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]],
@"whoops! check the hard-coded CFBundleVersion!");
NSCAssert([bundleIdentifier isEqualToString:[[NSBundle mainBundle] bundleIdentifier]],
@"whoops! check the hard-coded bundle identifier!");
NSDictionary *receipt = dictionaryWithAppStoreReceipt(receiptPath);
if (!receipt) {
return NO;
}
unsigned char uuidBytes[16];
NSUUID *vendorUUID = [[UIDevice currentDevice] identifierForVendor];
[vendorUUID getUUIDBytes:uuidBytes];
NSMutableData *input = [NSMutableData data];
[input appendBytes:uuidBytes length:sizeof(uuidBytes)];
[input appendData:[receipt objectForKey:kReceiptOpaqueValue]];
[input appendData:[receipt objectForKey:kReceiptBundleIdentifierData]];
NSMutableData *hash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
SHA1([input bytes], [input length], [hash mutableBytes]);
if ([bundleIdentifier isEqualToString:[receipt objectForKey:kReceiptBundleIdentifier]] &&
[bundleVersion isEqualToString:[receipt objectForKey:kReceiptVersion]] &&
[hash isEqualToData:[receipt objectForKey:kReceiptHash]]) {
return YES;
}
return NO;
}
================================================
FILE: VerifyStoreReceipt.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1D79532E183835580062D190 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D79532D183835580062D190 /* Foundation.framework */; };
1D795330183835580062D190 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D79532F183835580062D190 /* CoreGraphics.framework */; };
1D795332183835580062D190 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D795331183835580062D190 /* UIKit.framework */; };
1D795338183835580062D190 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1D795336183835580062D190 /* InfoPlist.strings */; };
1D79533A183835580062D190 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D795339183835580062D190 /* main.m */; };
1D79533E183835580062D190 /* RMAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D79533D183835580062D190 /* RMAppDelegate.m */; };
1D795340183835580062D190 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1D79533F183835580062D190 /* Images.xcassets */; };
1D795347183835580062D190 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D795346183835580062D190 /* XCTest.framework */; };
1D795348183835580062D190 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D79532D183835580062D190 /* Foundation.framework */; };
1D795349183835580062D190 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D795331183835580062D190 /* UIKit.framework */; };
1D79535E183835D80062D190 /* VerifyStoreReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D79535D183835D80062D190 /* VerifyStoreReceipt.m */; };
1D795360183836170062D190 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D79535F183836170062D190 /* Security.framework */; };
1D7953631838367B0062D190 /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D7953611838367B0062D190 /* libcrypto.a */; };
1D7953641838367B0062D190 /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D7953621838367B0062D190 /* libssl.a */; };
1D795366183837D70062D190 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D795365183837D70062D190 /* StoreKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
1D79534A183835580062D190 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 1D795322183835570062D190 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 1D795329183835580062D190;
remoteInfo = VerifyStoreReceipt;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
1D79532A183835580062D190 /* VerifyStoreReceipt.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VerifyStoreReceipt.app; sourceTree = BUILT_PRODUCTS_DIR; };
1D79532D183835580062D190 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1D79532F183835580062D190 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
1D795331183835580062D190 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
1D795335183835580062D190 /* VerifyStoreReceipt-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "VerifyStoreReceipt-Info.plist"; sourceTree = "<group>"; };
1D795337183835580062D190 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1D795339183835580062D190 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
1D79533B183835580062D190 /* VerifyStoreReceipt-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VerifyStoreReceipt-Prefix.pch"; sourceTree = "<group>"; };
1D79533C183835580062D190 /* RMAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RMAppDelegate.h; sourceTree = "<group>"; };
1D79533D183835580062D190 /* RMAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RMAppDelegate.m; sourceTree = "<group>"; };
1D79533F183835580062D190 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
1D795345183835580062D190 /* VerifyStoreReceiptTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VerifyStoreReceiptTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1D795346183835580062D190 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
1D79535C183835D80062D190 /* VerifyStoreReceipt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VerifyStoreReceipt.h; sourceTree = "<group>"; };
1D79535D183835D80062D190 /* VerifyStoreReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VerifyStoreReceipt.m; sourceTree = "<group>"; };
1D79535F183836170062D190 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
1D7953611838367B0062D190 /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = "../OpenSSL-for-iPhone/lib/libcrypto.a"; sourceTree = "<group>"; };
1D7953621838367B0062D190 /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = "../OpenSSL-for-iPhone/lib/libssl.a"; sourceTree = "<group>"; };
1D795365183837D70062D190 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1D795327183835580062D190 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1D795366183837D70062D190 /* StoreKit.framework in Frameworks */,
1D7953631838367B0062D190 /* libcrypto.a in Frameworks */,
1D7953641838367B0062D190 /* libssl.a in Frameworks */,
1D795360183836170062D190 /* Security.framework in Frameworks */,
1D795330183835580062D190 /* CoreGraphics.framework in Frameworks */,
1D795332183835580062D190 /* UIKit.framework in Frameworks */,
1D79532E183835580062D190 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
1D795342183835580062D190 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1D795347183835580062D190 /* XCTest.framework in Frameworks */,
1D795349183835580062D190 /* UIKit.framework in Frameworks */,
1D795348183835580062D190 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1D795321183835570062D190 = {
isa = PBXGroup;
children = (
1D795333183835580062D190 /* VerifyStoreReceipt */,
1D79532C183835580062D190 /* Frameworks */,
1D79532B183835580062D190 /* Products */,
);
sourceTree = "<group>";
};
1D79532B183835580062D190 /* Products */ = {
isa = PBXGroup;
children = (
1D79532A183835580062D190 /* VerifyStoreReceipt.app */,
1D795345183835580062D190 /* VerifyStoreReceiptTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
1D79532C183835580062D190 /* Frameworks */ = {
isa = PBXGroup;
children = (
1D795365183837D70062D190 /* StoreKit.framework */,
1D7953611838367B0062D190 /* libcrypto.a */,
1D7953621838367B0062D190 /* libssl.a */,
1D79535F183836170062D190 /* Security.framework */,
1D79532D183835580062D190 /* Foundation.framework */,
1D79532F183835580062D190 /* CoreGraphics.framework */,
1D795331183835580062D190 /* UIKit.framework */,
1D795346183835580062D190 /* XCTest.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
1D795333183835580062D190 /* VerifyStoreReceipt */ = {
isa = PBXGroup;
children = (
1D79533C183835580062D190 /* RMAppDelegate.h */,
1D79533D183835580062D190 /* RMAppDelegate.m */,
1D79535C183835D80062D190 /* VerifyStoreReceipt.h */,
1D79535D183835D80062D190 /* VerifyStoreReceipt.m */,
1D79533F183835580062D190 /* Images.xcassets */,
1D795334183835580062D190 /* Supporting Files */,
);
path = VerifyStoreReceipt;
sourceTree = "<group>";
};
1D795334183835580062D190 /* Supporting Files */ = {
isa = PBXGroup;
children = (
1D795335183835580062D190 /* VerifyStoreReceipt-Info.plist */,
1D795336183835580062D190 /* InfoPlist.strings */,
1D795339183835580062D190 /* main.m */,
1D79533B183835580062D190 /* VerifyStoreReceipt-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1D795329183835580062D190 /* VerifyStoreReceipt */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1D795356183835580062D190 /* Build configuration list for PBXNativeTarget "VerifyStoreReceipt" */;
buildPhases = (
1D795326183835580062D190 /* Sources */,
1D795327183835580062D190 /* Frameworks */,
1D795328183835580062D190 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = VerifyStoreReceipt;
productName = VerifyStoreReceipt;
productReference = 1D79532A183835580062D190 /* VerifyStoreReceipt.app */;
productType = "com.apple.product-type.application";
};
1D795344183835580062D190 /* VerifyStoreReceiptTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1D795359183835580062D190 /* Build configuration list for PBXNativeTarget "VerifyStoreReceiptTests" */;
buildPhases = (
1D795341183835580062D190 /* Sources */,
1D795342183835580062D190 /* Frameworks */,
1D795343183835580062D190 /* Resources */,
);
buildRules = (
);
dependencies = (
1D79534B183835580062D190 /* PBXTargetDependency */,
);
name = VerifyStoreReceiptTests;
productName = VerifyStoreReceiptTests;
productReference = 1D795345183835580062D190 /* VerifyStoreReceiptTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
1D795322183835570062D190 /* Project object */ = {
isa = PBXProject;
attributes = {
CLASSPREFIX = RM;
LastUpgradeCheck = 0500;
ORGANIZATIONNAME = "Rick Maddy";
TargetAttributes = {
1D795344183835580062D190 = {
TestTargetID = 1D795329183835580062D190;
};
};
};
buildConfigurationList = 1D795325183835570062D190 /* Build configuration list for PBXProject "VerifyStoreReceipt" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 1D795321183835570062D190;
productRefGroup = 1D79532B183835580062D190 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1D795329183835580062D190 /* VerifyStoreReceipt */,
1D795344183835580062D190 /* VerifyStoreReceiptTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1D795328183835580062D190 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1D795338183835580062D190 /* InfoPlist.strings in Resources */,
1D795340183835580062D190 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
1D795343183835580062D190 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1D795326183835580062D190 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1D79533A183835580062D190 /* main.m in Sources */,
1D79533E183835580062D190 /* RMAppDelegate.m in Sources */,
1D79535E183835D80062D190 /* VerifyStoreReceipt.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
1D795341183835580062D190 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
1D79534B183835580062D190 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 1D795329183835580062D190 /* VerifyStoreReceipt */;
targetProxy = 1D79534A183835580062D190 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
1D795336183835580062D190 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
1D795337183835580062D190 /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
1D795354183835580062D190 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
1D795355183835580062D190 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
1D795357183835580062D190 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch";
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"../OpenSSL-for-iPhone/include",
);
INFOPLIST_FILE = "VerifyStoreReceipt/VerifyStoreReceipt-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"/Users/rick/projects/home/iphone/OpenSSL-for-iPhone/lib",
);
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Debug;
};
1D795358183835580062D190 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch";
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"../OpenSSL-for-iPhone/include",
);
INFOPLIST_FILE = "VerifyStoreReceipt/VerifyStoreReceipt-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"/Users/rick/projects/home/iphone/OpenSSL-for-iPhone/lib",
);
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Release;
};
1D79535A183835580062D190 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/VerifyStoreReceipt.app/VerifyStoreReceipt";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
"$(DEVELOPER_FRAMEWORKS_DIR)",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = "VerifyStoreReceiptTests/VerifyStoreReceiptTests-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUNDLE_LOADER)";
WRAPPER_EXTENSION = xctest;
};
name = Debug;
};
1D79535B183835580062D190 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/VerifyStoreReceipt.app/VerifyStoreReceipt";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
"$(DEVELOPER_FRAMEWORKS_DIR)",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch";
INFOPLIST_FILE = "VerifyStoreReceiptTests/VerifyStoreReceiptTests-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUNDLE_LOADER)";
WRAPPER_EXTENSION = xctest;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1D795325183835570062D190 /* Build configuration list for PBXProject "VerifyStoreReceipt" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1D795354183835580062D190 /* Debug */,
1D795355183835580062D190 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1D795356183835580062D190 /* Build configuration list for PBXNativeTarget "VerifyStoreReceipt" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1D795357183835580062D190 /* Debug */,
1D795358183835580062D190 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
1D795359183835580062D190 /* Build configuration list for PBXNativeTarget "VerifyStoreReceiptTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1D79535A183835580062D190 /* Debug */,
1D79535B183835580062D190 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
};
rootObject = 1D795322183835570062D190 /* Project object */;
}
================================================
FILE: VerifyStoreReceipt.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:VerifyStoreReceipt.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: VerifyStoreReceipt.xcodeproj/project.xcworkspace/xcuserdata/rick.xcuserdatad/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
<true/>
<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
<false/>
</dict>
</plist>
gitextract_qhdmevxp/
├── LICENSE
├── README.md
├── VerifyStoreReceipt/
│ ├── Images.xcassets/
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage/
│ │ └── Contents.json
│ ├── RMAppDelegate.h
│ ├── RMAppDelegate.m
│ ├── VerifyStoreReceipt-Info.plist
│ ├── VerifyStoreReceipt-Prefix.pch
│ ├── VerifyStoreReceipt.h
│ ├── VerifyStoreReceipt.m
│ ├── en.lproj/
│ │ └── InfoPlist.strings
│ └── main.m
├── VerifyStoreReceipt.h
├── VerifyStoreReceipt.m
└── VerifyStoreReceipt.xcodeproj/
├── project.pbxproj
└── project.xcworkspace/
├── contents.xcworkspacedata
└── xcuserdata/
└── rick.xcuserdatad/
├── UserInterfaceState.xcuserstate
└── WorkspaceSettings.xcsettings
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (87K chars).
[
{
"path": "LICENSE",
"chars": 1288,
"preview": "Copyright (c) 2013, rmaddy\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modi"
},
{
"path": "README.md",
"chars": 3886,
"preview": "# VerifyStoreReceiptiOS\nRick Maddy, Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo, yene, Dav"
},
{
"path": "VerifyStoreReceipt/Images.xcassets/AppIcon.appiconset/Contents.json",
"chars": 825,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"size\" : \"29x29\",\n \"scale\" : \"2x\"\n },\n {\n \"idiom\""
},
{
"path": "VerifyStoreReceipt/Images.xcassets/LaunchImage.launchimage/Contents.json",
"chars": 1100,
"preview": "{\n \"images\" : [\n {\n \"orientation\" : \"portrait\",\n \"idiom\" : \"iphone\",\n \"extent\" : \"full-screen\",\n "
},
{
"path": "VerifyStoreReceipt/RMAppDelegate.h",
"chars": 291,
"preview": "//\n// RMAppDelegate.h\n// VerifyStoreReceipt\n//\n// Created by Rick Maddy on 11/16/13.\n// Copyright (c) 2013 Rick Madd"
},
{
"path": "VerifyStoreReceipt/RMAppDelegate.m",
"chars": 5742,
"preview": "//\n// RMAppDelegate.m\n// VerifyStoreReceipt\n//\n// Created by Rick Maddy on 11/16/13.\n// Copyright (c) 2013 Rick Madd"
},
{
"path": "VerifyStoreReceipt/VerifyStoreReceipt-Info.plist",
"chars": 1467,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch",
"chars": 344,
"preview": "//\n// Prefix header\n//\n// The contents of this file are implicitly included at the beginning of every source file.\n//\n"
},
{
"path": "VerifyStoreReceipt/VerifyStoreReceipt.h",
"chars": 2774,
"preview": "//\n// VerifyStoreReceipt.h\n//\n// Based on validatereceipt.h\n// Created by Ruotger Skupin on 23.10.10.\n// Copyright 2"
},
{
"path": "VerifyStoreReceipt/VerifyStoreReceipt.m",
"chars": 18347,
"preview": "//\n// validatereceipt.m\n//\n// Based on validatereceipt.m\n// Created by Ruotger Skupin on 23.10.10.\n// Copyright 2010"
},
{
"path": "VerifyStoreReceipt/en.lproj/InfoPlist.strings",
"chars": 45,
"preview": "/* Localized versions of Info.plist keys */\n\n"
},
{
"path": "VerifyStoreReceipt/main.m",
"chars": 351,
"preview": "//\n// main.m\n// VerifyStoreReceipt\n//\n// Created by Rick Maddy on 11/16/13.\n// Copyright (c) 2013 Rick Maddy. All ri"
},
{
"path": "VerifyStoreReceipt.h",
"chars": 2774,
"preview": "//\n// VerifyStoreReceipt.h\n//\n// Based on validatereceipt.h\n// Created by Ruotger Skupin on 23.10.10.\n// Copyright 2"
},
{
"path": "VerifyStoreReceipt.m",
"chars": 18347,
"preview": "//\n// validatereceipt.m\n//\n// Based on validatereceipt.m\n// Created by Ruotger Skupin on 23.10.10.\n// Copyright 2010"
},
{
"path": "VerifyStoreReceipt.xcodeproj/project.pbxproj",
"chars": 20585,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "VerifyStoreReceipt.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 163,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:VerifyStoreRece"
},
{
"path": "VerifyStoreReceipt.xcodeproj/project.xcworkspace/xcuserdata/rick.xcuserdatad/WorkspaceSettings.xcsettings",
"chars": 333,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the rmaddy/VerifyStoreReceiptiOS GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (76.8 KB), approximately 21.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.