Shark Deng
Shark Deng

Reputation: 1078

why cannot read the receipt data for on-device validation

I am using the following code to read the receipt data. I can successfully validate the receipt signature by using OpenSSL static library 1.1.1k

 private func readReceipt(_ receiptPKCS7: UnsafeMutablePointer<PKCS7>?) {
        // Get a pointer to the start and end of the ASN.1 payload
        let receiptSign = receiptPKCS7?.pointee.d.sign
        let octets = receiptSign?.pointee.contents.pointee.d.data
        var ptr = UnsafePointer(octets?.pointee.data)
        let end = ptr!.advanced(by: Int(octets!.pointee.length))
      
        var type: Int32 = 0
        var xclass: Int32 = 0
        var length: Int = 0
      
        ASN1_get_object(&ptr, &length, &type, &xclass, ptr!.distance(to: end))
        guard type == V_ASN1_SET else {
            status = .unexpectedASN1Type
            return
        }

        // 1
        while ptr! < end {
            // 2
            ASN1_get_object(&ptr, &length, &type, &xclass, ptr!.distance(to: end))
            guard type == V_ASN1_SEQUENCE else {
                status = .unexpectedASN1Type
                return
            }
            
        
            // 3 type
            guard let attributeType = readASN1Integer(ptr: &ptr, maxLength: length) else {
              status = .unexpectedASN1Type
              return
            }
            print("shark-IAP, ", attributeType)
            
            // 4 version
            guard let _ = readASN1Integer(ptr: &ptr, maxLength: ptr!.distance(to: end)) else {
                print("shark-IAP, 3")
              status = .unexpectedASN1Type
              return
            }
            
            // 5 value
            ASN1_get_object(&ptr, &length, &type, &xclass, ptr!.distance(to: end))
            guard type == V_ASN1_OCTET_STRING else {
                print("shark-IAP, 4")
              status = .unexpectedASN1Type
              return
            }
            
            switch attributeType {
                case 2: // The bundle identifier
                    var stringStartPtr = ptr
                    bundleIdString = readASN1String(ptr: &stringStartPtr, maxLength: length)
                    bundleIdData = readASN1Data(ptr: ptr!, length: length)
              
                case 3: // Bundle version
                    var stringStartPtr = ptr
                    bundleVersionString = readASN1String(ptr: &stringStartPtr, maxLength: length)
              
                case 4: // Opaque value
                    let dataStartPtr = ptr!
                    opaqueData = readASN1Data(ptr: dataStartPtr, length: length)
              
                case 5: // Computed GUID (SHA-1 Hash)
                    let dataStartPtr = ptr!
                    hashData = readASN1Data(ptr: dataStartPtr, length: length)
              
                case 12: // Receipt Creation Date
                    var dateStartPtr = ptr
                    receiptCreationDate = readASN1Date(ptr: &dateStartPtr, maxLength: length)
              
                case 17: // IAP Receipt
                    var iapStartPtr = ptr
                    let parsedReceipt = IAPReceipt(with: &iapStartPtr, payloadLength: length)
                    if let newReceipt = parsedReceipt {
                        inAppReceipts.append(newReceipt)
                    }
              
                case 19: // Original App Version
                    var stringStartPtr = ptr
                    originalAppVersion = readASN1String(ptr: &stringStartPtr, maxLength: length)
              
                case 21: // Expiration Date
                    var dateStartPtr = ptr
                    expirationDate = readASN1Date(ptr: &dateStartPtr, maxLength: length)
              
                default: // Ignore other attributes in receipt
                    print("Not processing attribute type: \(attributeType)")
            }
            
            // Advance pointer to the next item
            ptr = ptr!.advanced(by: length)
        } // end while
    }



func readASN1Integer(ptr: inout UnsafePointer<UInt8>?, maxLength: Int) -> Int? {
    var type: Int32 = 0
    var xclass: Int32 = 0
    var length: Int = 0
  
    ASN1_get_object(&ptr, &length, &type, &xclass, maxLength)
    
    guard type == V_ASN1_INTEGER else {
        print("shark-IAP no!", type)
        return nil
    }
   
//     let integerObject = c2i_ASN1_INTEGER(nil, &ptr, length)
    let integerObject = d2i_ASN1_UINTEGER(nil, &ptr, length)
    let intValue = ASN1_INTEGER_get(integerObject)
    ASN1_INTEGER_free(integerObject)
  
    
    return intValue
}

I got these print outputs. I suspect the function readASN1Integer is wrong. Maybe c2i_ASN1_INTEGER will be fine but this is deprecated in OpenSSL 1.1*, that d2i_ASN1_UINTEGER is used instead. And d2i_ASN1_UINTEGER needs to pass (identifier + length/octet + content), not just the content. In ASN1_get_object, the pointer has changed position. So d2i_ASN1_UINTEGER reads wrong. The first readASN1Integer causes bias that the second readASN1Integer throws error.

shark-IAP,  0
shark-IAP no! 8
shark-IAP, 3
shark-IAP, bundleVersionString nil
shark-IAP, expirationData nil
shark-IAP,  0
shark-IAP no! 8
shark-IAP, 3
shark-IAP, bundleVersionString nil
shark-IAP, expirationData nil

But I dont' know how to adjust the code to suit d2i_ASN1_UINTEGER. Thank you for your help!

Upvotes: 2

Views: 296

Answers (1)

Shark Deng
Shark Deng

Reputation: 1078

Stackoverflow is a place to suppress devils.

I found the solution. I modified the readASN1Integer to this

func readASN1Integer(ptr: inout UnsafePointer<UInt8>?, maxLength: Int) -> Int? {
    var type: Int32 = 0
    var xclass: Int32 = 0
    var length: Int = 0
    let save_ptr = ptr
    ASN1_get_object(&ptr, &length, &type, &xclass, maxLength)

    guard type == V_ASN1_INTEGER else {
        return nil
    }
   
//     let integerObject = c2i_ASN1_INTEGER(nil, &ptr, length)
    ptr = save_ptr
    let integerObject = d2i_ASN1_UINTEGER(nil, &ptr, maxLength)
    let intValue = ASN1_INTEGER_get(integerObject)
    ASN1_INTEGER_free(integerObject)
  
    
    return intValue
}
  1. Since the pointer position is changed, need to set it back, that's why save_ptr comes (referenced from c2i_ASN1_INTEGER function in Openssl 1.1.0)
  2. d2i_ASN1_UINTEGER(nil, &ptr, length) is changed to d2i_ASN1_UINTEGER(nil, &ptr, maxLength). For every place, including in readReceipt, length should be maxLength or ptr!.distance(to: end).

Upvotes: 7

Related Questions