Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 431 lines (339 sloc) 12.33 kB
bddbcd1 @roddi initial commit
authored
1 //
2 // validatereceipt.m
3 //
4 // Created by Ruotger Skupin on 23.10.10.
fcd6d2c @roddi cleaned up, added anlumo to copyright notice and README
authored
5 // Copyright 2010 Matthew Stevens, Ruotger Skupin, Apple, Dave Carlton, Fraser Hess, anlumo. All rights reserved.
bddbcd1 @roddi initial commit
authored
6 //
7
eb16b8f @roddi added BSD-style license; tweaked build settings; corrected pointer co…
authored
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
bddbcd1 @roddi initial commit
authored
27 #import "validatereceipt.h"
28
fcd6d2c @roddi cleaned up, added anlumo to copyright notice and README
authored
29 // link with Foundation.framework, IOKit.framework, Security.framework and libCrypto (via -lcrypto in Other Linker Flags)
bddbcd1 @roddi initial commit
authored
30
31 #import <IOKit/IOKitLib.h>
32 #import <Foundation/Foundation.h>
33
9fef320 @roddi working certificate check
authored
34 #import <Security/Security.h>
35
bddbcd1 @roddi initial commit
authored
36 #include <openssl/pkcs7.h>
37 #include <openssl/objects.h>
38 #include <openssl/sha.h>
9fef320 @roddi working certificate check
authored
39 #include <openssl/x509.h>
40 #include <openssl/err.h>
bddbcd1 @roddi initial commit
authored
41
f22472b cosmetic changes
yene authored
42 //#define USE_SAMPLE_RECEIPT // also defined in the debug build settings
9d599b3 @roddi added proto for copy_mac_address(); added warnings for when it is com…
authored
43
44 #ifdef USE_SAMPLE_RECEIPT
45 #warning ************************************
46 #warning ******* USES SAMPLE RECEIPT! *******
47 #warning ************************************
48 #endif
49
bddbcd1 @roddi initial commit
authored
50
eb16b8f @roddi added BSD-style license; tweaked build settings; corrected pointer co…
authored
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
ec42914 @kgn Moving VRCFRelease to a better place
kgn authored
64
1051075 @kgn Adding CFRelease macro
kgn authored
65 #define VRCFRelease(object) if(object) CFRelease(object)
eb16b8f @roddi added BSD-style license; tweaked build settings; corrected pointer co…
authored
66
bddbcd1 @roddi initial commit
authored
67 NSString *kReceiptBundleIdentifer = @"BundleIdentifier";
68 NSString *kReceiptBundleIdentiferData = @"BundleIdentifierData";
69 NSString *kReceiptVersion = @"Version";
70 NSString *kReceiptOpaqueValue = @"OpaqueValue";
71 NSString *kReceiptHash = @"Hash";
72
9fef320 @roddi working certificate check
authored
73
f22472b cosmetic changes
yene authored
74 NSData * appleRootCert(void)
9fef320 @roddi working certificate check
authored
75 {
76 OSStatus status;
77
78 SecKeychainRef keychain = nil;
79 status = SecKeychainOpen("/System/Library/Keychains/SystemRootCertificates.keychain", &keychain);
1051075 @kgn Adding CFRelease macro
kgn authored
80 if(status){
81 VRCFRelease(keychain);
9fef320 @roddi working certificate check
authored
82 return nil;
83 }
84
85 CFArrayRef searchList = CFArrayCreate(kCFAllocatorDefault, (const void**)&keychain, 1, &kCFTypeArrayCallBacks);
86
1051075 @kgn Adding CFRelease macro
kgn authored
87 VRCFRelease(keychain);
9fef320 @roddi working certificate check
authored
88
89 SecKeychainSearchRef searchRef = nil;
90 status = SecKeychainSearchCreateFromAttributes(searchList, kSecCertificateItemClass, NULL, &searchRef);
1051075 @kgn Adding CFRelease macro
kgn authored
91 if(status){
9e9f7d6 @kgn Spaces to tabs
kgn authored
92 VRCFRelease(searchRef);
93 VRCFRelease(searchList);
9fef320 @roddi working certificate check
authored
94 return nil;
95 }
96
97 SecKeychainItemRef itemRef = nil;
98 NSData * resultData = nil;
99
100 while(SecKeychainSearchCopyNext(searchRef, &itemRef) == noErr && resultData == nil) {
101 // Grab the name of the certificate
102 SecKeychainAttributeList list;
103 SecKeychainAttribute attributes[1];
104
105 attributes[0].tag = kSecLabelItemAttr;
106
107 list.count = 1;
108 list.attr = attributes;
109
110 SecKeychainItemCopyContent(itemRef, nil, &list, nil, nil);
111 NSData *nameData = [NSData dataWithBytesNoCopy:attributes[0].data length:attributes[0].length freeWhenDone:NO];
112 NSString *name = [[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding];
113
114 if([name isEqualToString:@"Apple Root CA"]) {
115 CSSM_DATA certData;
116 status = SecCertificateGetData((SecCertificateRef)itemRef, &certData);
1051075 @kgn Adding CFRelease macro
kgn authored
117 if(status){
9e9f7d6 @kgn Spaces to tabs
kgn authored
118 VRCFRelease(itemRef);
9fef320 @roddi working certificate check
authored
119 }
120
121 resultData = [NSData dataWithBytes:certData.Data length:certData.Length];
122
123 SecKeychainItemFreeContent(&list, NULL);
9e9f7d6 @kgn Spaces to tabs
kgn authored
124 VRCFRelease(itemRef);
9fef320 @roddi working certificate check
authored
125 }
126
9e9f7d6 @kgn Spaces to tabs
kgn authored
127 [name release];
9fef320 @roddi working certificate check
authored
128 }
9e9f7d6 @kgn Spaces to tabs
kgn authored
129 VRCFRelease(searchList);
130 VRCFRelease(searchRef);
9fef320 @roddi working certificate check
authored
131
132 return resultData;
133 }
134
135
bddbcd1 @roddi initial commit
authored
136 NSDictionary * dictionaryWithAppStoreReceipt(NSString * path)
137 {
9fef320 @roddi working certificate check
authored
138 NSData * rootCertData = appleRootCert();
139
9e9f7d6 @kgn Spaces to tabs
kgn authored
140 enum ATTRIBUTES
bddbcd1 @roddi initial commit
authored
141 {
9e9f7d6 @kgn Spaces to tabs
kgn authored
142 ATTR_START = 1,
143 BUNDLE_ID,
144 VERSION,
145 OPAQUE_VALUE,
146 HASH,
147 ATTR_END
148 };
149
9fef320 @roddi working certificate check
authored
150 ERR_load_PKCS7_strings();
151 ERR_load_X509_strings();
152 OpenSSL_add_all_digests();
153
9e9f7d6 @kgn Spaces to tabs
kgn authored
154 // Expected input is a PKCS7 container with signed data containing
155 // an ASN.1 SET of SEQUENCE structures. Each SEQUENCE contains
156 // two INTEGERS and an OCTET STRING.
157
bddbcd1 @roddi initial commit
authored
158 const char * receiptPath = [[path stringByStandardizingPath] fileSystemRepresentation];
9e9f7d6 @kgn Spaces to tabs
kgn authored
159 FILE *fp = fopen(receiptPath, "rb");
160 if (fp == NULL)
161 return nil;
162
163 PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
164 fclose(fp);
8d5c75d Fixed crash when the receipt file is invalid.
philippm authored
165
166 // Check if the receipt file was invalid (otherwise we go crashing and burning)
167 if (p7 == NULL) {
168 return nil;
169 }
9e9f7d6 @kgn Spaces to tabs
kgn authored
170
171 if (!PKCS7_type_is_signed(p7)) {
172 PKCS7_free(p7);
173 return nil;
174 }
175
176 if (!PKCS7_type_is_data(p7->d.sign->contents)) {
177 PKCS7_free(p7);
178 return nil;
179 }
180
91a8697 Proper allocation checks and cleanup for bio and X509.
Philippe Casgrain authored
181 int verifyReturnValue = 0;
9fef320 @roddi working certificate check
authored
182 X509_STORE *store = X509_STORE_new();
91a8697 Proper allocation checks and cleanup for bio and X509.
Philippe Casgrain authored
183 if (store)
184 {
75f7b19 @kgn Changes from yene's fork, this fixes my issue with warnings: issue 7
kgn authored
185 const unsigned char *data = (unsigned char *)(rootCertData.bytes);
eb16b8f @roddi added BSD-style license; tweaked build settings; corrected pointer co…
authored
186 X509 *appleCA = d2i_X509(NULL, &data, (long)rootCertData.length);
91a8697 Proper allocation checks and cleanup for bio and X509.
Philippe Casgrain authored
187 if (appleCA)
188 {
189 BIO *payload = BIO_new(BIO_s_mem());
190 X509_STORE_add_cert(store, appleCA);
191
192 if (payload)
193 {
194 verifyReturnValue = PKCS7_verify(p7,NULL,store,NULL,payload,0);
195 BIO_free(payload);
196 }
9fef320 @roddi working certificate check
authored
197
91a8697 Proper allocation checks and cleanup for bio and X509.
Philippe Casgrain authored
198 // this code will come handy when the first real receipts arrive
9fef320 @roddi working certificate check
authored
199 #if 0
91a8697 Proper allocation checks and cleanup for bio and X509.
Philippe Casgrain authored
200 unsigned long err = ERR_get_error();
201 if(err)
202 printf("%lu: %s\n",err,ERR_error_string(err,NULL));
203 else {
204 STACK_OF(X509) *stack = PKCS7_get0_signers(p7, NULL, 0);
205 for(NSUInteger i = 0; i < sk_num(stack); i++) {
206 const X509 *signer = (X509*)sk_value(stack, i);
207 NSLog(@"name = %s", signer->name);
208 }
209 }
210 #endif
211
212 X509_free(appleCA);
9fef320 @roddi working certificate check
authored
213 }
91a8697 Proper allocation checks and cleanup for bio and X509.
Philippe Casgrain authored
214 X509_STORE_free(store);
9fef320 @roddi working certificate check
authored
215 }
216 EVP_cleanup();
217
218 if (verifyReturnValue != 1)
fcd6d2c @roddi cleaned up, added anlumo to copyright notice and README
authored
219 {
9e9f7d6 @kgn Spaces to tabs
kgn authored
220 PKCS7_free(p7);
9fef320 @roddi working certificate check
authored
221 return nil;
fcd6d2c @roddi cleaned up, added anlumo to copyright notice and README
authored
222 }
9fef320 @roddi working certificate check
authored
223
9e9f7d6 @kgn Spaces to tabs
kgn authored
224 ASN1_OCTET_STRING *octets = p7->d.sign->contents->d.data;
75f7b19 @kgn Changes from yene's fork, this fixes my issue with warnings: issue 7
kgn authored
225 const unsigned char *p = octets->data;
9e9f7d6 @kgn Spaces to tabs
kgn authored
226 const unsigned char *end = p + octets->length;
227
228 int type = 0;
229 int xclass = 0;
230 long length = 0;
231
232 ASN1_get_object(&p, &length, &type, &xclass, end - p);
233 if (type != V_ASN1_SET) {
234 PKCS7_free(p7);
235 return nil;
236 }
237
238 NSMutableDictionary *info = [NSMutableDictionary dictionary];
239
240 while (p < end) {
241 ASN1_get_object(&p, &length, &type, &xclass, end - p);
242 if (type != V_ASN1_SEQUENCE)
243 break;
244
245 const unsigned char *seq_end = p + length;
246
247 int attr_type = 0;
248 int attr_version = 0;
249
250 // Attribute type
251 ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
252 if (type == V_ASN1_INTEGER && length == 1) {
253 attr_type = p[0];
254 }
255 p += length;
256
257 // Attribute version
258 ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
259 if (type == V_ASN1_INTEGER && length == 1) {
260 attr_version = p[0];
8cce890 @tgunr Added new static library target validateAS.
tgunr authored
261 attr_version = attr_version;
9e9f7d6 @kgn Spaces to tabs
kgn authored
262 }
263 p += length;
264
265 // Only parse attributes we're interested in
266 if (attr_type > ATTR_START && attr_type < ATTR_END) {
267 NSString *key;
268
269 ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
270 if (type == V_ASN1_OCTET_STRING) {
271
272 // Bytes
273 if (attr_type == BUNDLE_ID || attr_type == OPAQUE_VALUE || attr_type == HASH) {
274 NSData *data = [NSData dataWithBytes:p length:(NSUInteger)length];
275
276 switch (attr_type) {
277 case BUNDLE_ID:
278 // This is included for hash generation
279 key = kReceiptBundleIdentiferData;
280 break;
281 case OPAQUE_VALUE:
282 key = kReceiptOpaqueValue;
283 break;
284 case HASH:
285 key = kReceiptHash;
286 break;
287 }
288
289 [info setObject:data forKey:key];
290 }
291
292 // Strings
293 if (attr_type == BUNDLE_ID || attr_type == VERSION) {
294 int str_type = 0;
295 long str_length = 0;
75f7b19 @kgn Changes from yene's fork, this fixes my issue with warnings: issue 7
kgn authored
296 const unsigned char *str_p = p;
9e9f7d6 @kgn Spaces to tabs
kgn authored
297 ASN1_get_object(&str_p, &str_length, &str_type, &xclass, seq_end - str_p);
298 if (str_type == V_ASN1_UTF8STRING) {
299 NSString *string = [[[NSString alloc] initWithBytes:str_p
300 length:(NSUInteger)str_length
301 encoding:NSUTF8StringEncoding] autorelease];
bddbcd1 @roddi initial commit
authored
302
9e9f7d6 @kgn Spaces to tabs
kgn authored
303 switch (attr_type) {
304 case BUNDLE_ID:
305 key = kReceiptBundleIdentifer;
306 break;
307 case VERSION:
308 key = kReceiptVersion;
309 break;
310 }
311
312 [info setObject:string forKey:key];
313 }
314 }
315 }
316 p += length;
317 }
318
319 // Skip any remaining fields in this SEQUENCE
320 while (p < seq_end) {
321 ASN1_get_object(&p, &length, &type, &xclass, seq_end - p);
322 p += length;
323 }
324 }
325
326 PKCS7_free(p7);
327
328 return info;
bddbcd1 @roddi initial commit
authored
329 }
330
331
332
333 // Returns a CFData object, containing the machine's GUID.
334 CFDataRef copy_mac_address(void)
335 {
9e9f7d6 @kgn Spaces to tabs
kgn authored
336 kern_return_t kernResult;
337 mach_port_t master_port;
338 CFMutableDictionaryRef matchingDict;
339 io_iterator_t iterator;
340 io_object_t service;
341 CFDataRef macAddress = nil;
bddbcd1 @roddi initial commit
authored
342
9e9f7d6 @kgn Spaces to tabs
kgn authored
343 kernResult = IOMasterPort(MACH_PORT_NULL, &master_port);
344 if (kernResult != KERN_SUCCESS) {
345 printf("IOMasterPort returned %d\n", kernResult);
346 return nil;
347 }
bddbcd1 @roddi initial commit
authored
348
9e9f7d6 @kgn Spaces to tabs
kgn authored
349 matchingDict = IOBSDNameMatching(master_port, 0, "en0");
350 if(!matchingDict) {
351 printf("IOBSDNameMatching returned empty dictionary\n");
352 return nil;
353 }
bddbcd1 @roddi initial commit
authored
354
9e9f7d6 @kgn Spaces to tabs
kgn authored
355 kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator);
356 if (kernResult != KERN_SUCCESS) {
357 printf("IOServiceGetMatchingServices returned %d\n", kernResult);
358 return nil;
359 }
bddbcd1 @roddi initial commit
authored
360
9e9f7d6 @kgn Spaces to tabs
kgn authored
361 while((service = IOIteratorNext(iterator)) != 0)
362 {
363 io_object_t parentService;
bddbcd1 @roddi initial commit
authored
364
9e9f7d6 @kgn Spaces to tabs
kgn authored
365 kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService);
366 if(kernResult == KERN_SUCCESS)
367 {
368 VRCFRelease(macAddress);
369 macAddress = IORegistryEntryCreateCFProperty(parentService, CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
370 IOObjectRelease(parentService);
371 }
372 else {
373 printf("IORegistryEntryGetParentEntry returned %d\n", kernResult);
374 }
bddbcd1 @roddi initial commit
authored
375
9e9f7d6 @kgn Spaces to tabs
kgn authored
376 IOObjectRelease(service);
377 }
bddbcd1 @roddi initial commit
authored
378
9e9f7d6 @kgn Spaces to tabs
kgn authored
379 return macAddress;
bddbcd1 @roddi initial commit
authored
380 }
381
382 BOOL validateReceiptAtPath(NSString * path)
383 {
384 NSDictionary * receipt = dictionaryWithAppStoreReceipt(path);
adf2ab5 @fraserhess Validate bundle identifier and bundle version
fraserhess authored
385
bddbcd1 @roddi initial commit
authored
386 if (!receipt)
387 return NO;
388
2939033 @roddi initializing pointers to nil
authored
389 NSData * guidData = nil;
390 NSString *bundleVersion = nil;
391 NSString *bundleIdentifer = nil;
adf2ab5 @fraserhess Validate bundle identifier and bundle version
fraserhess authored
392 #ifndef USE_SAMPLE_RECEIPT
393 guidData = (NSData*)copy_mac_address();
bddbcd1 @roddi initial commit
authored
394
395 if ([NSGarbageCollector defaultCollector])
396 [[NSGarbageCollector defaultCollector] enableCollectorForPointer:guidData];
397 else
398 [guidData autorelease];
399
400 if (!guidData)
401 return NO;
adf2ab5 @fraserhess Validate bundle identifier and bundle version
fraserhess authored
402
403 bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
404 bundleIdentifer = [[NSBundle mainBundle] bundleIdentifier];
bddbcd1 @roddi initial commit
authored
405
adf2ab5 @fraserhess Validate bundle identifier and bundle version
fraserhess authored
406 #else
407 // Overwrite with example GUID for use with example receipt
408 unsigned char guid[] = { 0x00, 0x17, 0xf2, 0xc4, 0xbc, 0xc0 };
409 guidData = [NSData dataWithBytes:guid length:sizeof(guid)];
410 bundleVersion = @"1.0.2";
411 bundleIdentifer = @"com.example.SampleApp";
8cce890 @tgunr Added new static library target validateAS.
tgunr authored
412 #endif
bddbcd1 @roddi initial commit
authored
413
414 NSMutableData *input = [NSMutableData data];
415 [input appendData:guidData];
416 [input appendData:[receipt objectForKey:kReceiptOpaqueValue]];
417 [input appendData:[receipt objectForKey:kReceiptBundleIdentiferData]];
418
419 NSMutableData *hash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
420 SHA1([input bytes], [input length], [hash mutableBytes]);
adf2ab5 @fraserhess Validate bundle identifier and bundle version
fraserhess authored
421
422 if ([bundleIdentifer isEqualToString:[receipt objectForKey:kReceiptBundleIdentifer]] &&
423 [bundleVersion isEqualToString:[receipt objectForKey:kReceiptVersion]] &&
424 [hash isEqualToData:[receipt objectForKey:kReceiptHash]])
bddbcd1 @roddi initial commit
authored
425 {
426 return YES;
427 }
428
429 return NO;
430 }
Something went wrong with that request. Please try again.