├── .gitignore ├── README.markdown ├── ValidateStoreReceipt.m ├── ValidateStoreReceipt.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── roddi.xcuserdatad │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── roddi.xcuserdatad │ └── xcschemes │ ├── ValidateStoreReceipt.xcscheme │ ├── validateAS.xcscheme │ └── xcschememanagement.plist ├── ValidateStoreReceipt_Prefix.pch ├── validatereceipt.h └── validatereceipt.m /.gitignore: -------------------------------------------------------------------------------- 1 | *.pbxuser 2 | *.perspectivev3 3 | build 4 | receipt 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # ValidateStoreReceipt 2 | Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo, yene, David Keegan, Alessandro Segala. 3 | 4 | At the end of October 2010 Apple announced the App Store for Mac. The App Store will put a receipt into your app bundle, but 5 | won't implement any copy protection scheme. For details see [Validating App Store Receipts](https://developer.apple.com/devcenter/mac/documents/validating.html) (Developer membership needed) 6 | 7 | Unfortunately this document doesn't tell you how to process this receipt in detail, quote: 8 | 9 | The payload of the PKCS7 container is encoded using ASN.1, as described by ITU-T X.690. 10 | 11 | This validator parses and validates the payload and the PKCS7 container itself. 12 | 13 | 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. 14 | 15 | Missing from this project: 16 | 17 | - Apple's example receipt. (I WON'T ADD IT HERE, APPLE WON'T LIKE THAT, SO DON'T ASK!) 18 | - Any measures to make your app cracker proof. 19 | - If I understand Alessandro correctly, In-App purchases are only extracted but not validated. 20 | 21 | ## Installation 22 | 23 | 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 validatereceipt.m. 24 | 25 | ## Using It 26 | 27 | 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. 28 | 29 | ## License 30 | 31 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 32 | 33 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 34 | 35 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in 36 | the documentation and/or other materials provided with the distribution. 37 | 38 | Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived 39 | from this software without specific prior written permission. 40 | 41 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 42 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 43 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 44 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 45 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 46 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.m: -------------------------------------------------------------------------------- 1 | /* 2 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 3 | 4 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 5 | 6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in 7 | the documentation and/or other materials provided with the distribution. 8 | 9 | Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived 10 | from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 13 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 14 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 15 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 16 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 17 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 18 | */ 19 | #import 20 | 21 | #import "validatereceipt.h" 22 | 23 | const NSString * global_bundleVersion = @"1.0.2"; 24 | const NSString * global_bundleIdentifier = @"com.example.SampleApp"; 25 | 26 | int main (int argc, const char * argv[]) { 27 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 28 | 29 | // put the example receipt on the desktop (or change that path) 30 | NSString * pathToReceipt = @"~/Desktop/receipt"; 31 | 32 | // in your own code you have to do: 33 | // NSString * pathToReceipt = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents/_MASReceipt/receipt"]; 34 | // this example is not a bundle so it wont work here. 35 | 36 | if (!validateReceiptAtPath(pathToReceipt)) 37 | exit(173); 38 | 39 | NSLog(@"Hello, correctly validated World!"); 40 | [pool drain]; 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 757917171274DB9800D249AB /* validatereceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = DD5EBFFE1273472D00361AC6 /* validatereceipt.m */; }; 11 | 757917181274DB9D00D249AB /* validatereceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = DD5EBFFD1273472D00361AC6 /* validatereceipt.h */; }; 12 | 8DD76F9A0486AA7600D96B5E /* ValidateStoreReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* ValidateStoreReceipt.m */; settings = {ATTRIBUTES = (); }; }; 13 | 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; 14 | DD5EBFFF1273472D00361AC6 /* validatereceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = DD5EBFFE1273472D00361AC6 /* validatereceipt.m */; }; 15 | DD5EC0011273474F00361AC6 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD5EC0001273474F00361AC6 /* IOKit.framework */; }; 16 | DDDE9E931281FFD3003F581D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDDE9E921281FFD3003F581D /* Security.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 8; 23 | dstPath = /usr/share/man/man1/; 24 | dstSubfolderSpec = 0; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 1; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 08FB7796FE84155DC02AAC07 /* ValidateStoreReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ValidateStoreReceipt.m; sourceTree = ""; }; 33 | 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 34 | 32A70AAB03705E1F00C91783 /* ValidateStoreReceipt_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValidateStoreReceipt_Prefix.pch; sourceTree = ""; }; 35 | 757917141274DB8300D249AB /* libvalidateAS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libvalidateAS.a; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 8DD76FA10486AA7600D96B5E /* ValidateStoreReceipt */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ValidateStoreReceipt; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | DD5EBFFD1273472D00361AC6 /* validatereceipt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = validatereceipt.h; sourceTree = ""; }; 38 | DD5EBFFE1273472D00361AC6 /* validatereceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = validatereceipt.m; sourceTree = ""; }; 39 | DD5EC0001273474F00361AC6 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 40 | DDDE9E921281FFD3003F581D /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 41 | DDDE9F311282135A003F581D /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.markdown; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 757917121274DB8300D249AB /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, 57 | DD5EC0011273474F00361AC6 /* IOKit.framework in Frameworks */, 58 | DDDE9E931281FFD3003F581D /* Security.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | 08FB7794FE84155DC02AAC07 /* ValidateStoreReceipt */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 08FB7795FE84155DC02AAC07 /* Source */, 69 | C6859EA2029092E104C91782 /* Documentation */, 70 | 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, 71 | 1AB674ADFE9D54B511CA2CBB /* Products */, 72 | DD5EC0001273474F00361AC6 /* IOKit.framework */, 73 | DDDE9E921281FFD3003F581D /* Security.framework */, 74 | ); 75 | name = ValidateStoreReceipt; 76 | sourceTree = ""; 77 | }; 78 | 08FB7795FE84155DC02AAC07 /* Source */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | DD5EBFFD1273472D00361AC6 /* validatereceipt.h */, 82 | DD5EBFFE1273472D00361AC6 /* validatereceipt.m */, 83 | 32A70AAB03705E1F00C91783 /* ValidateStoreReceipt_Prefix.pch */, 84 | 08FB7796FE84155DC02AAC07 /* ValidateStoreReceipt.m */, 85 | ); 86 | name = Source; 87 | sourceTree = ""; 88 | }; 89 | 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 08FB779EFE84155DC02AAC07 /* Foundation.framework */, 93 | ); 94 | name = "External Frameworks and Libraries"; 95 | sourceTree = ""; 96 | }; 97 | 1AB674ADFE9D54B511CA2CBB /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 8DD76FA10486AA7600D96B5E /* ValidateStoreReceipt */, 101 | 757917141274DB8300D249AB /* libvalidateAS.a */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | C6859EA2029092E104C91782 /* Documentation */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | DDDE9F311282135A003F581D /* README.markdown */, 110 | ); 111 | name = Documentation; 112 | sourceTree = ""; 113 | }; 114 | /* End PBXGroup section */ 115 | 116 | /* Begin PBXHeadersBuildPhase section */ 117 | 757917101274DB8300D249AB /* Headers */ = { 118 | isa = PBXHeadersBuildPhase; 119 | buildActionMask = 2147483647; 120 | files = ( 121 | 757917181274DB9D00D249AB /* validatereceipt.h in Headers */, 122 | ); 123 | runOnlyForDeploymentPostprocessing = 0; 124 | }; 125 | /* End PBXHeadersBuildPhase section */ 126 | 127 | /* Begin PBXNativeTarget section */ 128 | 757917131274DB8300D249AB /* validateAS */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = 7579171B1274DBC400D249AB /* Build configuration list for PBXNativeTarget "validateAS" */; 131 | buildPhases = ( 132 | 757917101274DB8300D249AB /* Headers */, 133 | 757917111274DB8300D249AB /* Sources */, 134 | 757917121274DB8300D249AB /* Frameworks */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = validateAS; 141 | productName = validateAS; 142 | productReference = 757917141274DB8300D249AB /* libvalidateAS.a */; 143 | productType = "com.apple.product-type.library.static"; 144 | }; 145 | 8DD76F960486AA7600D96B5E /* ValidateStoreReceipt */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "ValidateStoreReceipt" */; 148 | buildPhases = ( 149 | 8DD76F990486AA7600D96B5E /* Sources */, 150 | 8DD76F9B0486AA7600D96B5E /* Frameworks */, 151 | 8DD76F9E0486AA7600D96B5E /* CopyFiles */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = ValidateStoreReceipt; 158 | productInstallPath = "$(HOME)/bin"; 159 | productName = ValidateStoreReceipt; 160 | productReference = 8DD76FA10486AA7600D96B5E /* ValidateStoreReceipt */; 161 | productType = "com.apple.product-type.tool"; 162 | }; 163 | /* End PBXNativeTarget section */ 164 | 165 | /* Begin PBXProject section */ 166 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 167 | isa = PBXProject; 168 | attributes = { 169 | LastUpgradeCheck = 0410; 170 | }; 171 | buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "ValidateStoreReceipt" */; 172 | compatibilityVersion = "Xcode 3.2"; 173 | developmentRegion = English; 174 | hasScannedForEncodings = 1; 175 | knownRegions = ( 176 | English, 177 | Japanese, 178 | French, 179 | German, 180 | ); 181 | mainGroup = 08FB7794FE84155DC02AAC07 /* ValidateStoreReceipt */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | 8DD76F960486AA7600D96B5E /* ValidateStoreReceipt */, 186 | 757917131274DB8300D249AB /* validateAS */, 187 | ); 188 | }; 189 | /* End PBXProject section */ 190 | 191 | /* Begin PBXSourcesBuildPhase section */ 192 | 757917111274DB8300D249AB /* Sources */ = { 193 | isa = PBXSourcesBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | 757917171274DB9800D249AB /* validatereceipt.m in Sources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | 8DD76F990486AA7600D96B5E /* Sources */ = { 201 | isa = PBXSourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 8DD76F9A0486AA7600D96B5E /* ValidateStoreReceipt.m in Sources */, 205 | DD5EBFFF1273472D00361AC6 /* validatereceipt.m in Sources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | /* End PBXSourcesBuildPhase section */ 210 | 211 | /* Begin XCBuildConfiguration section */ 212 | 1DEB927508733DD40010E9CD /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | COPY_PHASE_STRIP = NO; 217 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 218 | GCC_DYNAMIC_NO_PIC = NO; 219 | GCC_OPTIMIZATION_LEVEL = 0; 220 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 221 | GCC_PREFIX_HEADER = ValidateStoreReceipt_Prefix.pch; 222 | GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; 223 | GCC_TREAT_WARNINGS_AS_ERRORS = NO; 224 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 226 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 227 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 228 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 229 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 230 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 231 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 232 | GCC_WARN_MISSING_PARENTHESES = YES; 233 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; 234 | GCC_WARN_PEDANTIC = NO; 235 | GCC_WARN_PROTOTYPE_CONVERSION = YES; 236 | GCC_WARN_SHADOW = YES; 237 | GCC_WARN_SIGN_COMPARE = YES; 238 | GCC_WARN_STRICT_SELECTOR_MATCH = YES; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_LABEL = YES; 243 | GCC_WARN_UNUSED_VALUE = YES; 244 | INSTALL_PATH = /usr/local/bin; 245 | OTHER_LDFLAGS = "-lcrypto"; 246 | PRODUCT_NAME = ValidateStoreReceipt; 247 | RUN_CLANG_STATIC_ANALYZER = YES; 248 | }; 249 | name = Debug; 250 | }; 251 | 1DEB927608733DD40010E9CD /* Release */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 256 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 257 | GCC_PREFIX_HEADER = ValidateStoreReceipt_Prefix.pch; 258 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 259 | INSTALL_PATH = /usr/local/bin; 260 | OTHER_LDFLAGS = "-lcrypto"; 261 | PRODUCT_NAME = ValidateStoreReceipt; 262 | RUN_CLANG_STATIC_ANALYZER = YES; 263 | }; 264 | name = Release; 265 | }; 266 | 1DEB927908733DD40010E9CD /* Debug */ = { 267 | isa = XCBuildConfiguration; 268 | buildSettings = { 269 | ARCHS = ( 270 | x86_64, 271 | i386, 272 | ); 273 | GCC_C_LANGUAGE_STANDARD = gnu99; 274 | GCC_ENABLE_OBJC_GC = supported; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 278 | GCC_WARN_UNUSED_VARIABLE = YES; 279 | OTHER_CFLAGS = ( 280 | "-DYES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK", 281 | "-DUSE_SAMPLE_RECEIPT", 282 | ); 283 | RUN_CLANG_STATIC_ANALYZER = YES; 284 | SDKROOT = macosx10.6; 285 | }; 286 | name = Debug; 287 | }; 288 | 1DEB927A08733DD40010E9CD /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ARCHS = ( 292 | x86_64, 293 | i386, 294 | ); 295 | GCC_C_LANGUAGE_STANDARD = gnu99; 296 | GCC_ENABLE_OBJC_GC = supported; 297 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | RUN_CLANG_STATIC_ANALYZER = YES; 301 | SDKROOT = macosx10.6; 302 | }; 303 | name = Release; 304 | }; 305 | 757917151274DB8300D249AB /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | COPY_PHASE_STRIP = NO; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_MODEL_TUNING = G5; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 314 | GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; 315 | INSTALL_PATH = /usr/local/lib; 316 | OTHER_LDFLAGS = ( 317 | "-framework", 318 | Foundation, 319 | "-framework", 320 | AppKit, 321 | ); 322 | PRODUCT_NAME = validateAS; 323 | }; 324 | name = Debug; 325 | }; 326 | 757917161274DB8300D249AB /* Release */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | COPY_PHASE_STRIP = YES; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | GCC_MODEL_TUNING = G5; 333 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 334 | GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; 335 | INSTALL_PATH = /usr/local/lib; 336 | OTHER_LDFLAGS = ( 337 | "-framework", 338 | Foundation, 339 | "-framework", 340 | AppKit, 341 | ); 342 | PRODUCT_NAME = validateAS; 343 | ZERO_LINK = NO; 344 | }; 345 | name = Release; 346 | }; 347 | /* End XCBuildConfiguration section */ 348 | 349 | /* Begin XCConfigurationList section */ 350 | 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "ValidateStoreReceipt" */ = { 351 | isa = XCConfigurationList; 352 | buildConfigurations = ( 353 | 1DEB927508733DD40010E9CD /* Debug */, 354 | 1DEB927608733DD40010E9CD /* Release */, 355 | ); 356 | defaultConfigurationIsVisible = 0; 357 | defaultConfigurationName = Release; 358 | }; 359 | 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "ValidateStoreReceipt" */ = { 360 | isa = XCConfigurationList; 361 | buildConfigurations = ( 362 | 1DEB927908733DD40010E9CD /* Debug */, 363 | 1DEB927A08733DD40010E9CD /* Release */, 364 | ); 365 | defaultConfigurationIsVisible = 0; 366 | defaultConfigurationName = Release; 367 | }; 368 | 7579171B1274DBC400D249AB /* Build configuration list for PBXNativeTarget "validateAS" */ = { 369 | isa = XCConfigurationList; 370 | buildConfigurations = ( 371 | 757917151274DB8300D249AB /* Debug */, 372 | 757917161274DB8300D249AB /* Release */, 373 | ); 374 | defaultConfigurationIsVisible = 0; 375 | defaultConfigurationName = Release; 376 | }; 377 | /* End XCConfigurationList section */ 378 | }; 379 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; 380 | } 381 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.xcodeproj/project.xcworkspace/xcuserdata/roddi.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceUserSettings_HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | IDEWorkspaceUserSettings_SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.xcodeproj/xcuserdata/roddi.xcuserdatad/xcschemes/ValidateStoreReceipt.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 14 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 38 | 39 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 62 | 63 | 64 | 65 | 67 | 68 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.xcodeproj/xcuserdata/roddi.xcuserdatad/xcschemes/validateAS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 14 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 46 | 47 | 49 | 50 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ValidateStoreReceipt.xcodeproj/xcuserdata/roddi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ValidateStoreReceipt.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | validateAS.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 757917131274DB8300D249AB 21 | 22 | primary 23 | 24 | 25 | 8DD76F960486AA7600D96B5E 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ValidateStoreReceipt_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'ValidateStoreReceipt' target in the 'ValidateStoreReceipt' project. 3 | // 4 | 5 | /* 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in 11 | the documentation and/or other materials provided with the distribution. 12 | 13 | Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 17 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 18 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 21 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | 24 | #ifdef __OBJC__ 25 | #import 26 | #endif 27 | -------------------------------------------------------------------------------- /validatereceipt.h: -------------------------------------------------------------------------------- 1 | // 2 | // validatereceipt.h 3 | // 4 | // Created by Ruotger Skupin on 23.10.10. 5 | // Copyright 2010-2011 Matthew Stevens, Ruotger Skupin, Apple, Alessandro Segala. All rights reserved. 6 | // 7 | 8 | /* 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | 13 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in 14 | the documentation and/or other materials provided with the distribution. 15 | 16 | Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 20 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 21 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 24 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #import 28 | 29 | 30 | extern NSString *kReceiptBundleIdentifer; 31 | extern NSString *kReceiptBundleIdentiferData; 32 | extern NSString *kReceiptVersion; 33 | extern NSString *kReceiptOpaqueValue; 34 | extern NSString *kReceiptHash; 35 | extern NSString *kReceiptInApp; 36 | 37 | extern NSString *kReceiptInAppQuantity; 38 | extern NSString *kReceiptInAppProductIdentifier; 39 | extern NSString *kReceiptInAppTransactionIdentifier; 40 | extern NSString *kReceiptInAppPurchaseDate; 41 | extern NSString *kReceiptInAppOriginalTransactionIdentifier; 42 | extern NSString *kReceiptInAppOriginalPurchaseDate; 43 | 44 | CFDataRef copy_mac_address(void); 45 | 46 | NSArray * parseInAppPurchasesData(NSData * inappData); 47 | NSDictionary * dictionaryWithAppStoreReceipt(NSString * path); 48 | NSArray* obtainInAppPurchases(NSString *receiptPath); 49 | BOOL validateReceiptAtPath(NSString * path); 50 | NSData * appleRootCert(void); 51 | -------------------------------------------------------------------------------- /validatereceipt.m: -------------------------------------------------------------------------------- 1 | // 2 | // validatereceipt.m 3 | // 4 | // Created by Ruotger Skupin on 23.10.10. 5 | // Copyright 2010-2011 Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo, David Keegan, Alessandro Segala. All rights reserved. 6 | // 7 | 8 | /* 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | 13 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in 14 | the documentation and/or other materials provided with the distribution. 15 | 16 | Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 20 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 21 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 24 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #import "validatereceipt.h" 28 | 29 | // link with Foundation.framework, IOKit.framework, Security.framework and libCrypto (via -lcrypto in Other Linker Flags) 30 | 31 | #import 32 | #import 33 | 34 | #import 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | //#define USE_SAMPLE_RECEIPT // also defined in the debug build settings 43 | 44 | #ifdef USE_SAMPLE_RECEIPT 45 | #warning ************************************ 46 | #warning ******* USES SAMPLE RECEIPT! ******* 47 | #warning ************************************ 48 | #endif 49 | 50 | 51 | #ifndef YES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK 52 | 53 | #warning --- DON'T USE THIS CODE AS IS! IF EVERYONE USES THE SAME CODE 54 | #warning --- IT IS PRETTY EASY TO BUILD AN AUTOMATIC CRACKING TOOL 55 | #warning --- FOR APPS USING THIS CODE! 56 | #warning --- BY USING THIS CODE YOU ACCEPT TAKING THE RESPONSIBILITY FOR 57 | #warning --- ANY DAMAGE! 58 | #warning --- 59 | #warning --- YOU HAVE BEEN WARNED! 60 | 61 | // 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" 62 | 63 | #endif // YES_I_HAVE_READ_THE_WARNING_AND_I_ACCEPT_THE_RISK 64 | 65 | #define VRCFRelease(object) if(object) CFRelease(object) 66 | 67 | NSString *kReceiptBundleIdentifier = @"BundleIdentifier"; 68 | NSString *kReceiptBundleIdentifierData = @"BundleIdentifierData"; 69 | NSString *kReceiptVersion = @"Version"; 70 | NSString *kReceiptOpaqueValue = @"OpaqueValue"; 71 | NSString *kReceiptHash = @"Hash"; 72 | NSString *kReceiptInApp = @"InApp"; 73 | 74 | NSString *kReceiptInAppQuantity = @"Quantity"; 75 | NSString *kReceiptInAppProductIdentifier = @"ProductIdentifier"; 76 | NSString *kReceiptInAppTransactionIdentifier = @"TransactionIdentifier"; 77 | NSString *kReceiptInAppPurchaseDate = @"PurchaseDate"; 78 | NSString *kReceiptInAppOriginalTransactionIdentifier = @"OriginalTransactionIdentifier"; 79 | NSString *kReceiptInAppOriginalPurchaseDate = @"OriginalPurchaseDate"; 80 | 81 | 82 | NSData * appleRootCert(void) 83 | { 84 | OSStatus status; 85 | 86 | SecKeychainRef keychain = nil; 87 | status = SecKeychainOpen("/System/Library/Keychains/SystemRootCertificates.keychain", &keychain); 88 | if(status){ 89 | VRCFRelease(keychain); 90 | return nil; 91 | } 92 | 93 | CFArrayRef searchList = CFArrayCreate(kCFAllocatorDefault, (const void**)&keychain, 1, &kCFTypeArrayCallBacks); 94 | 95 | // For some reason we get a malloc reference underflow warning message when garbage collection 96 | // is on. Perhaps a bug in SecKeychainOpen where the keychain reference isn't actually retained 97 | // in GC? 98 | #ifndef __OBJC_GC__ 99 | VRCFRelease(keychain); 100 | #endif 101 | 102 | SecKeychainSearchRef searchRef = nil; 103 | status = SecKeychainSearchCreateFromAttributes(searchList, kSecCertificateItemClass, NULL, &searchRef); 104 | if(status){ 105 | VRCFRelease(searchRef); 106 | VRCFRelease(searchList); 107 | return nil; 108 | } 109 | 110 | SecKeychainItemRef itemRef = nil; 111 | NSData * resultData = nil; 112 | 113 | while(SecKeychainSearchCopyNext(searchRef, &itemRef) == noErr && resultData == nil) { 114 | // Grab the name of the certificate 115 | SecKeychainAttributeList list; 116 | SecKeychainAttribute attributes[1]; 117 | 118 | attributes[0].tag = kSecLabelItemAttr; 119 | 120 | list.count = 1; 121 | list.attr = attributes; 122 | 123 | SecKeychainItemCopyContent(itemRef, nil, &list, nil, nil); 124 | NSData *nameData = [NSData dataWithBytesNoCopy:attributes[0].data length:attributes[0].length freeWhenDone:NO]; 125 | NSString *name = [[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding]; 126 | 127 | if([name isEqualToString:@"Apple Root CA"]) { 128 | CSSM_DATA certData; 129 | SecCertificateGetData((SecCertificateRef)itemRef, &certData); 130 | resultData = [NSData dataWithBytes:certData.Data length:certData.Length]; 131 | } 132 | 133 | SecKeychainItemFreeContent(&list, NULL); 134 | 135 | if (itemRef) 136 | VRCFRelease(itemRef); 137 | 138 | [name release]; 139 | } 140 | 141 | VRCFRelease(searchList); 142 | VRCFRelease(searchRef); 143 | 144 | return resultData; 145 | } 146 | 147 | 148 | NSArray * parseInAppPurchasesData(NSData * inappData) 149 | { 150 | #define INAPP_ATTR_START 1700 151 | #define INAPP_QUANTITY 1701 152 | #define INAPP_PRODID 1702 153 | #define INAPP_TRANSID 1703 154 | #define INAPP_PURCHDATE 1704 155 | #define INAPP_ORIGTRANSID 1705 156 | #define INAPP_ORIGPURCHDATE 1706 157 | #define INAPP_ATTR_END 1707 158 | 159 | int type = 0; 160 | int xclass = 0; 161 | long length = 0; 162 | 163 | NSUInteger dataLenght = [inappData length]; 164 | const uint8_t *p = [inappData bytes]; 165 | 166 | const uint8_t *end = p + dataLenght; 167 | 168 | NSMutableArray *resultArray = [NSMutableArray array]; 169 | 170 | while (p < end) 171 | { 172 | ASN1_get_object(&p, &length, &type, &xclass, end - p); 173 | 174 | const uint8_t *set_end = p + length; 175 | 176 | if(type != V_ASN1_SET) { 177 | break; 178 | } 179 | 180 | NSMutableDictionary *item = [[NSMutableDictionary alloc] initWithCapacity:6]; 181 | 182 | while (p < set_end) { 183 | ASN1_get_object(&p, &length, &type, &xclass, set_end - p); 184 | if (type != V_ASN1_SEQUENCE) 185 | break; 186 | 187 | const uint8_t *seq_end = p + length; 188 | 189 | int attr_type = 0; 190 | int attr_version = 0; 191 | 192 | // Attribute type 193 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 194 | if (type == V_ASN1_INTEGER) { 195 | if(length == 1) { 196 | attr_type = p[0]; 197 | } 198 | else if(length == 2) { 199 | attr_type = p[0] * 0x100 + p[1] 200 | ; 201 | } 202 | } 203 | p += length; 204 | 205 | // Attribute version 206 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 207 | if (type == V_ASN1_INTEGER && length == 1) { 208 | // clang analyser hit (wontfix at the moment, since the code might come in handy later) 209 | // But if someone has a convincing case throwing that out, I might do so, Roddi 210 | attr_version = p[0]; 211 | } 212 | p += length; 213 | 214 | // Only parse attributes we're interested in 215 | if (attr_type > INAPP_ATTR_START && attr_type < INAPP_ATTR_END) { 216 | NSString *key = nil; 217 | 218 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 219 | if (type == V_ASN1_OCTET_STRING) { 220 | //NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length]; 221 | 222 | // Integers 223 | if(attr_type == INAPP_QUANTITY) { 224 | int num_type = 0; 225 | long num_length = 0; 226 | const uint8_t *num_p = p; 227 | ASN1_get_object(&num_p, &num_length, &num_type, &xclass, seq_end - num_p); 228 | if(num_type == V_ASN1_INTEGER) { 229 | NSUInteger quantity = 0; 230 | if(num_length) { 231 | quantity += num_p[0]; 232 | if(num_length > 1) { 233 | quantity += num_p[1] * 0x100; 234 | if(num_length > 2) { 235 | quantity += num_p[2] * 0x10000; 236 | if(num_length > 3) { 237 | quantity += num_p[3] * 0x1000000; 238 | } 239 | } 240 | } 241 | } 242 | 243 | NSNumber *num = [[NSNumber alloc] initWithUnsignedInteger:quantity]; 244 | [item setObject:num forKey:kReceiptInAppQuantity]; 245 | [num release]; 246 | } 247 | } 248 | 249 | // Strings 250 | if (attr_type == INAPP_PRODID || 251 | attr_type == INAPP_TRANSID || 252 | attr_type == INAPP_ORIGTRANSID || 253 | attr_type == INAPP_PURCHDATE || 254 | attr_type == INAPP_ORIGPURCHDATE) { 255 | 256 | int str_type = 0; 257 | long str_length = 0; 258 | const uint8_t *str_p = p; 259 | ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p); 260 | if (str_type == V_ASN1_UTF8STRING) { 261 | switch (attr_type) { 262 | case INAPP_PRODID: 263 | key = kReceiptInAppProductIdentifier; 264 | break; 265 | case INAPP_TRANSID: 266 | key = kReceiptInAppTransactionIdentifier; 267 | break; 268 | case INAPP_ORIGTRANSID: 269 | key = kReceiptInAppOriginalTransactionIdentifier; 270 | break; 271 | } 272 | 273 | if (key) { 274 | NSString *string = [[NSString alloc] initWithBytes:str_p 275 | length:(NSUInteger)str_length 276 | encoding:NSUTF8StringEncoding]; 277 | [item setObject:string forKey:key]; 278 | [string release]; 279 | } 280 | } 281 | if (str_type == V_ASN1_IA5STRING) { 282 | switch (attr_type) { 283 | case INAPP_PURCHDATE: 284 | key = kReceiptInAppPurchaseDate; 285 | break; 286 | case INAPP_ORIGPURCHDATE: 287 | key = kReceiptInAppOriginalPurchaseDate; 288 | break; 289 | } 290 | 291 | if (key) { 292 | NSString *string = [[NSString alloc] initWithBytes:str_p 293 | length:(NSUInteger)str_length 294 | encoding:NSASCIIStringEncoding]; 295 | [item setObject:string forKey:key]; 296 | [string release]; 297 | } 298 | } 299 | } 300 | } 301 | 302 | p += length; 303 | } 304 | 305 | // Skip any remaining fields in this SEQUENCE 306 | while (p < seq_end) { 307 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 308 | p += length; 309 | } 310 | } 311 | 312 | // Skip any remaining fields in this SET 313 | while (p < set_end) { 314 | ASN1_get_object(&p, &length, &type, &xclass, set_end - p); 315 | p += length; 316 | } 317 | 318 | [resultArray addObject:item]; 319 | [item release]; 320 | } 321 | 322 | return resultArray; 323 | } 324 | 325 | 326 | NSDictionary * dictionaryWithAppStoreReceipt(NSString * path) 327 | { 328 | NSData * rootCertData = appleRootCert(); 329 | 330 | #define ATTR_START 1 331 | #define BUNDLE_ID 2 332 | #define VERSION 3 333 | #define OPAQUE_VALUE 4 334 | #define HASH 5 335 | #define ATTR_END 6 336 | #define INAPP_PURCHASE 17 337 | 338 | ERR_load_PKCS7_strings(); 339 | ERR_load_X509_strings(); 340 | OpenSSL_add_all_digests(); 341 | 342 | // Expected input is a PKCS7 container with signed data containing 343 | // an ASN.1 SET of SEQUENCE structures. Each SEQUENCE contains 344 | // two INTEGERS and an OCTET STRING. 345 | 346 | const char * receiptPath = [[path stringByStandardizingPath] fileSystemRepresentation]; 347 | FILE *fp = fopen(receiptPath, "rb"); 348 | if (fp == NULL) 349 | return nil; 350 | 351 | PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL); 352 | fclose(fp); 353 | 354 | // Check if the receipt file was invalid (otherwise we go crashing and burning) 355 | if (p7 == NULL) { 356 | return nil; 357 | } 358 | 359 | if (!PKCS7_type_is_signed(p7)) { 360 | PKCS7_free(p7); 361 | return nil; 362 | } 363 | 364 | if (!PKCS7_type_is_data(p7->d.sign->contents)) { 365 | PKCS7_free(p7); 366 | return nil; 367 | } 368 | 369 | int verifyReturnValue = 0; 370 | X509_STORE *store = X509_STORE_new(); 371 | if (store) 372 | { 373 | const uint8_t *data = (uint8_t *)(rootCertData.bytes); 374 | X509 *appleCA = d2i_X509(NULL, &data, (long)rootCertData.length); 375 | if (appleCA) 376 | { 377 | BIO *payload = BIO_new(BIO_s_mem()); 378 | X509_STORE_add_cert(store, appleCA); 379 | 380 | if (payload) 381 | { 382 | verifyReturnValue = PKCS7_verify(p7,NULL,store,NULL,payload,0); 383 | BIO_free(payload); 384 | } 385 | 386 | // this code will come handy when the first real receipts arrive 387 | #if 0 388 | unsigned long err = ERR_get_error(); 389 | if(err) 390 | printf("%lu: %s\n",err,ERR_error_string(err,NULL)); 391 | else { 392 | STACK_OF(X509) *stack = PKCS7_get0_signers(p7, NULL, 0); 393 | for(NSUInteger i = 0; i < sk_num(stack); i++) { 394 | const X509 *signer = (X509*)sk_value(stack, i); 395 | NSLog(@"name = %s", signer->name); 396 | } 397 | } 398 | #endif 399 | 400 | X509_free(appleCA); 401 | } 402 | X509_STORE_free(store); 403 | } 404 | EVP_cleanup(); 405 | 406 | if (verifyReturnValue != 1) 407 | { 408 | PKCS7_free(p7); 409 | return nil; 410 | } 411 | 412 | ASN1_OCTET_STRING *octets = p7->d.sign->contents->d.data; 413 | const uint8_t *p = octets->data; 414 | const uint8_t *end = p + octets->length; 415 | 416 | int type = 0; 417 | int xclass = 0; 418 | long length = 0; 419 | 420 | ASN1_get_object(&p, &length, &type, &xclass, end - p); 421 | if (type != V_ASN1_SET) { 422 | PKCS7_free(p7); 423 | return nil; 424 | } 425 | 426 | NSMutableDictionary *info = [NSMutableDictionary dictionary]; 427 | 428 | while (p < end) { 429 | ASN1_get_object(&p, &length, &type, &xclass, end - p); 430 | if (type != V_ASN1_SEQUENCE) 431 | break; 432 | 433 | const uint8_t *seq_end = p + length; 434 | 435 | int attr_type = 0; 436 | int attr_version = 0; 437 | 438 | // Attribute type 439 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 440 | if (type == V_ASN1_INTEGER && length == 1) { 441 | attr_type = p[0]; 442 | } 443 | p += length; 444 | 445 | // Attribute version 446 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 447 | if (type == V_ASN1_INTEGER && length == 1) { 448 | attr_version = p[0]; 449 | attr_version = attr_version; 450 | } 451 | p += length; 452 | 453 | // Only parse attributes we're interested in 454 | if ((attr_type > ATTR_START && attr_type < ATTR_END) || attr_type == INAPP_PURCHASE) { 455 | NSString *key = nil; 456 | 457 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 458 | if (type == V_ASN1_OCTET_STRING) { 459 | NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length]; 460 | 461 | // Bytes 462 | if (attr_type == BUNDLE_ID || attr_type == OPAQUE_VALUE || attr_type == HASH) { 463 | switch (attr_type) { 464 | case BUNDLE_ID: 465 | // This is included for hash generation 466 | key = kReceiptBundleIdentifierData; 467 | break; 468 | case OPAQUE_VALUE: 469 | key = kReceiptOpaqueValue; 470 | break; 471 | case HASH: 472 | key = kReceiptHash; 473 | break; 474 | } 475 | if (key) { 476 | [info setObject:data forKey:key]; 477 | } 478 | } 479 | 480 | // Strings 481 | if (attr_type == BUNDLE_ID || attr_type == VERSION) { 482 | int str_type = 0; 483 | long str_length = 0; 484 | const uint8_t *str_p = p; 485 | ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p); 486 | if (str_type == V_ASN1_UTF8STRING) { 487 | switch (attr_type) { 488 | case BUNDLE_ID: 489 | key = kReceiptBundleIdentifier; 490 | break; 491 | case VERSION: 492 | key = kReceiptVersion; 493 | break; 494 | } 495 | 496 | if (key) { 497 | NSString *string = [[NSString alloc] initWithBytes:str_p 498 | length:(NSUInteger)str_length 499 | encoding:NSUTF8StringEncoding]; 500 | [info setObject:string forKey:key]; 501 | [string release]; 502 | } 503 | } 504 | } 505 | 506 | // In-App purchases 507 | if (attr_type == INAPP_PURCHASE) 508 | { 509 | NSArray *inApp = parseInAppPurchasesData(data); 510 | [info setObject:inApp forKey:kReceiptInApp]; 511 | } 512 | } 513 | p += length; 514 | } 515 | 516 | // Skip any remaining fields in this SEQUENCE 517 | while (p < seq_end) { 518 | ASN1_get_object(&p, &length, &type, &xclass, seq_end - p); 519 | p += length; 520 | } 521 | } 522 | 523 | PKCS7_free(p7); 524 | 525 | return info; 526 | } 527 | 528 | 529 | 530 | // Returns a CFData object, containing the machine's GUID. 531 | CFDataRef copy_mac_address(void) 532 | { 533 | kern_return_t kernResult; 534 | mach_port_t master_port; 535 | CFMutableDictionaryRef matchingDict; 536 | io_iterator_t iterator; 537 | io_object_t service; 538 | CFDataRef macAddress = nil; 539 | 540 | kernResult = IOMasterPort(MACH_PORT_NULL, &master_port); 541 | if (kernResult != KERN_SUCCESS) { 542 | printf("IOMasterPort returned %d\n", kernResult); 543 | return nil; 544 | } 545 | 546 | matchingDict = IOBSDNameMatching(master_port, 0, "en0"); 547 | if(!matchingDict) { 548 | printf("IOBSDNameMatching returned empty dictionary\n"); 549 | return nil; 550 | } 551 | 552 | kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator); 553 | if (kernResult != KERN_SUCCESS) { 554 | printf("IOServiceGetMatchingServices returned %d\n", kernResult); 555 | return nil; 556 | } 557 | 558 | while((service = IOIteratorNext(iterator)) != 0) 559 | { 560 | io_object_t parentService; 561 | 562 | kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService); 563 | if(kernResult == KERN_SUCCESS) 564 | { 565 | VRCFRelease(macAddress); 566 | macAddress = IORegistryEntryCreateCFProperty(parentService, CFSTR("IOMACAddress"), kCFAllocatorDefault, 0); 567 | IOObjectRelease(parentService); 568 | } 569 | else { 570 | printf("IORegistryEntryGetParentEntry returned %d\n", kernResult); 571 | } 572 | 573 | IOObjectRelease(service); 574 | } 575 | 576 | return macAddress; 577 | } 578 | 579 | NSArray* obtainInAppPurchases(NSString *receiptPath) 580 | { 581 | // According to the documentation, we need to validate the receipt first. 582 | // If the receipt is not valid, no In-App purchase is valid. 583 | // This performs a "quick" validation. Please use validateReceiptAtPath to perform a full validation. 584 | 585 | NSDictionary * receipt = dictionaryWithAppStoreReceipt(receiptPath); 586 | if (!receipt) 587 | return nil; 588 | 589 | NSArray *purchases = [receipt objectForKey:kReceiptInApp]; 590 | if(!purchases || ![purchases isKindOfClass:[NSArray class]]) 591 | return nil; 592 | 593 | return purchases; 594 | } 595 | 596 | extern const NSString * global_bundleVersion; 597 | extern const NSString * global_bundleIdentifier; 598 | 599 | // in your project define those two somewhere as such: 600 | // const NSString * global_bundleVersion = @"1.0.2"; 601 | // const NSString * global_bundleIdentifier = @"com.example.SampleApp"; 602 | 603 | BOOL validateReceiptAtPath(NSString * path) 604 | { 605 | // it turns out, it's a bad idea, to use these two NSBundle methods in your app: 606 | // 607 | // bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 608 | // bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; 609 | // 610 | // http://www.craftymind.com/2011/01/06/mac-app-store-hacked-how-developers-can-better-protect-themselves/ 611 | // 612 | // so use hard coded values instead (probably even somehow obfuscated) 613 | 614 | // analyser warning when USE_SAMPLE_RECEIPT is defined (wontfix) 615 | NSString *bundleVersion = (NSString*)global_bundleVersion; 616 | NSString *bundleIdentifier = (NSString*)global_bundleIdentifier; 617 | #ifndef USE_SAMPLE_RECEIPT 618 | // avoid making stupid mistakes --> check again 619 | NSCAssert([bundleVersion isEqualToString:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]], 620 | @"whoops! check the hard-coded CFBundleShortVersionString!"); 621 | NSCAssert([bundleIdentifier isEqualToString:[[NSBundle mainBundle] bundleIdentifier]], 622 | @"whoops! check the hard-coded bundle identifier!"); 623 | #else 624 | bundleVersion = @"1.0.2"; 625 | bundleIdentifier = @"com.example.SampleApp"; 626 | #endif 627 | NSDictionary * receipt = dictionaryWithAppStoreReceipt(path); 628 | 629 | if (!receipt) 630 | return NO; 631 | 632 | NSData * guidData = nil; 633 | #ifndef USE_SAMPLE_RECEIPT 634 | guidData = (NSData*)copy_mac_address(); 635 | 636 | if ([NSGarbageCollector defaultCollector]) 637 | [[NSGarbageCollector defaultCollector] enableCollectorForPointer:guidData]; 638 | else 639 | [guidData autorelease]; 640 | 641 | if (!guidData) 642 | return NO; 643 | #else 644 | // Overwrite with example GUID for use with example receipt 645 | unsigned char guid[] = { 0x00, 0x17, 0xf2, 0xc4, 0xbc, 0xc0 }; 646 | guidData = [NSData dataWithBytes:guid length:sizeof(guid)]; 647 | #endif 648 | 649 | NSMutableData *input = [NSMutableData data]; 650 | [input appendData:guidData]; 651 | [input appendData:[receipt objectForKey:kReceiptOpaqueValue]]; 652 | [input appendData:[receipt objectForKey:kReceiptBundleIdentifierData]]; 653 | 654 | NSMutableData *hash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH]; 655 | SHA1([input bytes], [input length], [hash mutableBytes]); 656 | 657 | if ([bundleIdentifier isEqualToString:[receipt objectForKey:kReceiptBundleIdentifier]] && 658 | [bundleVersion isEqualToString:[receipt objectForKey:kReceiptVersion]] && 659 | [hash isEqualToData:[receipt objectForKey:kReceiptHash]]) 660 | { 661 | return YES; 662 | } 663 | 664 | return NO; 665 | } 666 | --------------------------------------------------------------------------------