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 @interface RMAppDelegate : UIResponder @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 // 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 () @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 ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.maddyhome.iphone.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: VerifyStoreReceipt/VerifyStoreReceipt-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_3_0 #warning "This project uses features only available in iOS SDK 3.0 and later." #endif #ifdef __OBJC__ #import #import #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 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 #include #include #include #include #include #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 #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 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 #include #include #include #include #include #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 = ""; }; 1D795337183835580062D190 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 1D795339183835580062D190 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1D79533B183835580062D190 /* VerifyStoreReceipt-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VerifyStoreReceipt-Prefix.pch"; sourceTree = ""; }; 1D79533C183835580062D190 /* RMAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RMAppDelegate.h; sourceTree = ""; }; 1D79533D183835580062D190 /* RMAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RMAppDelegate.m; sourceTree = ""; }; 1D79533F183835580062D190 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 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 = ""; }; 1D79535D183835D80062D190 /* VerifyStoreReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VerifyStoreReceipt.m; sourceTree = ""; }; 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 = ""; }; 1D7953621838367B0062D190 /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = "../OpenSSL-for-iPhone/lib/libssl.a"; sourceTree = ""; }; 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 = ""; }; 1D79532B183835580062D190 /* Products */ = { isa = PBXGroup; children = ( 1D79532A183835580062D190 /* VerifyStoreReceipt.app */, 1D795345183835580062D190 /* VerifyStoreReceiptTests.xctest */, ); name = Products; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 1D795334183835580062D190 /* Supporting Files */ = { isa = PBXGroup; children = ( 1D795335183835580062D190 /* VerifyStoreReceipt-Info.plist */, 1D795336183835580062D190 /* InfoPlist.strings */, 1D795339183835580062D190 /* main.m */, 1D79533B183835580062D190 /* VerifyStoreReceipt-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; /* 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 = ""; }; /* 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 ================================================ ================================================ FILE: VerifyStoreReceipt.xcodeproj/project.xcworkspace/xcuserdata/rick.xcuserdatad/WorkspaceSettings.xcsettings ================================================ HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges SnapshotAutomaticallyBeforeSignificantChanges