Skip to content

Instantly share code, notes, and snippets.

@mhomol
Created September 17, 2015 13:13
Show Gist options
  • Save mhomol/7ce6c8569d48234aeb41 to your computer and use it in GitHub Desktop.
Save mhomol/7ce6c8569d48234aeb41 to your computer and use it in GitHub Desktop.
Bag Labs Post - In-App Purchase Validation - Receipt Validation Part 2
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