Created
September 17, 2015 13:13
-
-
Save mhomol/7ce6c8569d48234aeb41 to your computer and use it in GitHub Desktop.
Bag Labs Post - In-App Purchase Validation - Receipt Validation Part 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let octets = pkcs7_d_data(pkcs7_d_sign(receiptPKCS7).memory.contents) | |
var ptr = UnsafePointer<UInt8>(octets.memory.data) | |
let end = ptr.advancedBy(Int(octets.memory.length)) | |
var type: Int32 = 0 | |
var xclass: Int32 = 0 | |
var length = 0 | |
ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr) | |
if (type != V_ASN1_SET) { | |
println("failed to read ASN1 from receipt") | |
return | |
} | |
var bundleIdString1: NSString? | |
var bundleVersionString1: NSString? | |
var bundleIdData1: NSData? | |
var hashData1: NSData? | |
var opaqueData1: NSData? | |
while (ptr < end) | |
{ | |
var integer: UnsafeMutablePointer<ASN1_INTEGER> | |
// Expecting an attribute sequence | |
ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr) | |
if type != V_ASN1_SEQUENCE { | |
println("ASN1 error: expected an attribute sequence") | |
} | |
let seq_end = ptr.advancedBy(length) | |
var attr_type = 0 | |
var attr_version = 0 | |
// The attribute is an integer | |
ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr) | |
if type != V_ASN1_INTEGER { | |
println("ASN1 error: attribute not an integer") | |
return | |
} | |
integer = c2i_ASN1_INTEGER(nil, &ptr, length) | |
attr_type = ASN1_INTEGER_get(integer) | |
ASN1_INTEGER_free(integer) | |
// The version is an integer | |
ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr) | |
if type != V_ASN1_INTEGER { | |
println("ASN1 error: version not an integer") | |
return | |
} | |
integer = c2i_ASN1_INTEGER(nil, &ptr, length); | |
attr_version = ASN1_INTEGER_get(integer); | |
ASN1_INTEGER_free(integer); | |
// The attribute value is an octet string | |
ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr) | |
if type != V_ASN1_OCTET_STRING { | |
println("ASN1 error: value not an octet string") | |
return | |
} | |
if attr_type == 2 { | |
// Bundle identifier | |
var str_ptr = ptr | |
var str_type: Int32 = 0 | |
var str_length = 0 | |
var str_xclass: Int32 = 0 | |
ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr) | |
if str_type == V_ASN1_UTF8STRING { | |
bundleIdString1 = NSString(bytes: str_ptr, length: str_length, encoding: NSUTF8StringEncoding) | |
bundleIdData1 = NSData(bytes: ptr, length: length) | |
} | |
} | |
else if attr_type == 3 { | |
// Bundle version | |
var str_ptr = ptr | |
var str_type: Int32 = 0 | |
var str_length = 0 | |
var str_xclass: Int32 = 0 | |
ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr) | |
if str_type == V_ASN1_UTF8STRING { | |
bundleVersionString1 = NSString(bytes: str_ptr, length: str_length, encoding: NSUTF8StringEncoding) | |
} | |
} | |
else if attr_type == 4 { | |
// Opaque value | |
opaqueData1 = NSData(bytes: ptr, length: length) | |
} | |
else if attr_type == 5 { | |
// Computed GUID (SHA-1 Hash) | |
hashData1 = NSData(bytes: ptr, length: length) | |
} | |
else if attr_type == 17 { | |
//In app receipt | |
var r = NSData(bytes: ptr, length: length) | |
//I wrote that method that reads through all the fields in the | |
//receipt, since I am only interested in the product Ids, that's | |
//all I pull out | |
var id = self.getProductIdFromReceipt(r) | |
//Do what you need to with the product Id | |
} | |
// Move past the value | |
ptr = ptr.advancedBy(length) | |
} | |
//Make sure that expected values from the receipt are actually there | |
if bundleIdString1 == nil { | |
println("No Bundle Id Found") | |
return | |
} | |
if bundleVersionString1 == nil { | |
println("No Bundle Version String Found") | |
return | |
} | |
if opaqueData1 == nil { | |
println("No Opaque Data Found") | |
return | |
} | |
if hashData1 == nil { | |
println("No Hash Value Found") | |
return | |
} | |
//Verify the bundle id in the receipt matches the app's bundle id, use hard coded value instead of pulling | |
//from info.plist since the plist can be changed by anyone that knows anything | |
if bundleIdString1 != "com.yourcompany.yourapp" { | |
println("Receipt verification error: Wrong bundle identifier") | |
return | |
} | |
// Retrieve the Device GUID | |
var device = UIDevice.currentDevice() | |
var uuid = device.identifierForVendor | |
let mutableData = NSMutableData(length: 16) | |
uuid.getUUIDBytes(UnsafeMutablePointer(mutableData!.mutableBytes)) | |
// Verify the hash | |
var hash = Array<UInt8>(count: 20, repeatedValue: 0) | |
var ctx = SHA_CTX() | |
SHA1_Init(&ctx) | |
SHA1_Update(&ctx, mutableData!.bytes, mutableData!.length) | |
SHA1_Update(&ctx, opaqueData1!.bytes, opaqueData1!.length) | |
SHA1_Update(&ctx, bundleIdData1!.bytes, bundleIdData1!.length) | |
SHA1_Final(&hash, &ctx) | |
let computedHashData1 = NSData(bytes: &hash, length: 20) | |
if !computedHashData1.isEqualToData(hashData1!) | |
{ | |
println("Receipt Hash Did Not Match!") | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment