ios 凭据验证_iOS内购IAP(十四) —— IAP的收据验证(一)

本文详细介绍了iOS应用内购IAP的收据验证过程,包括收据的概念、加载收据、验证Apple签名、读取和验证收据数据、处理应用内购买以及保护验证代码的方法。通过示例代码展示了如何使用OpenSSL库进行收据的验证,确保用户已为购买的商品付款。
摘要由CSDN通过智能技术生成

版本记录

版本号

时间

V1.0

2019.01.09

前言

开始

首先看下写作环境

Swift 4.2, iOS 12, Xcode 10

在本教程中,您将了解应用内购买的收据如何工作以及如何验证它们,以确保您的用户已为您提供的商品付款。

付费软件一直存在一个问题,即某些用户试图在不购买软件的情况下使用该软件或欺诈性地访问应用内购买。 收据提供了确认这些购买的工具。 他们通过提供销售记录来实现这一目标。 每当用户购买应用程序,进行应用内购买或更新应用程序时,App Store都会在应用程序包中生成收据。

在本教程中,您将了解这些收据的工作原理以及它们在设备上的验证方式。 在本教程中,您应该熟悉应用内购买和StoreKit。 您将需要一个iOS开发人员帐户,一个用于测试的真实设备,访问iOS开发人员中心和App Store Connect。

What Is a Receipt?

收据包含应用程序包中的单个文件。 该文件采用称为PKCS#7的格式。 这是应用了加密技术的数据的标准格式。 容器包含有效负载(payload),证书链(chain of certificates)和数字签名(digital signature)。 您使用证书链和数字签名来验证Apple是否生成了收据。

有效负载(payload)由一组称为ASN.1的跨平台格式的凭据属性组成。 这些属性中的每一个都包含类型,版本和值(type, version and value)。 这些代表收据的内容。 您的应用使用这些属性来确定收据对设备有效以及用户购买了什么。

Loading the Receipt

打开入门项目。入门项目是支持StoreKit和应用内购买的iPhone应用程序。

要测试收据验证,您必须在真实设备上运行该应用程序,因为它在模拟器中不起作用。您需要开发证书和沙盒帐户。通过XCode测试应用程序时,默认情况下应用程序不会有收据。如果不存在,则starter app会实现请求刷新的证书。

加密代码很复杂,很容易出错。最好使用已知且经过验证的库,而不是尝试编写自己的库。本教程使用OpenSSL库来完成验证加密和解码收据中提供的ASN.1数据的大部分工作。 OpenSSL不是非常Swift友好的,所以在本教程中你将创建一个Swift包装器。

为iPhone编译OpenSSL并不是一个简单的过程。如果您想自己动手,可以在GitHub上找到脚本和说明。入门项目包括OpenSSL文件夹中最新版本的OpenSSL 1.1.1。它被编译为静态库,使修改更加困难。这包括文件夹以及C头文件。该项目还包括使用Swift的OpenSSL库的桥接头。

注意:您可能想知道为什么使用OpenSSL而不是iOS内置的CommonCrypto框架,而且静态OpenSSL库为您的应用程序包添加了大约40MB。 原因是如果用户越狱他们的设备,使用黑客版本替换CommonCrypto将很容易解决这些问题。 bundle中的静态库是一个更难攻击的目标。

入门项目包括一个起始的Receipt类。 它还包含一个静态方法:isReceiptPresent()。 此方法确定是否存在收据文件。 如果没有,它会使用StoreKit在尝试验证之前请求刷新收据。 如果收据不存在,您的应用应该做类似的事情。

打开Receipt.swift。 在类声明结束时为类添加新的自定义初始值设定项:

init() {

guard let payload = loadReceipt() else {

return

}

}

要开始验证,您需要将收据作为Data对象。 将以下新方法添加到init()下面的Receipt以加载收据并返回PKCS#7数据结构:

private func loadReceipt() -> UnsafeMutablePointer? {

// Load the receipt into a Data object

guard

let receiptUrl = Bundle.main.appStoreReceiptURL,

let receiptData = try? Data(contentsOf: receiptUrl)

else {

receiptStatus = .noReceiptPresent

return nil

}

}

此代码获取收据的位置,并尝试将其作为Data对象加载。 如果不存在收据或收据不会作为Data对象加载,则验证失败。 如果在验证收据期间的任何时候检查失败,则整个验证失败。 代码将原因存储在类的receiptStatus属性中。

现在您在Data对象中有了收据,您可以使用OpenSSL处理内容。 OpenSSL函数是用C语言编写的,通常使用指针和其他底层方法。 在loadReceipt()的末尾添加以下代码:

// 1

let receiptBIO = BIO_new(BIO_s_mem())

let receiptBytes: [UInt8] = .init(receiptData)

BIO_write(receiptBIO, receiptBytes, Int32(receiptData.count))

// 2

let receiptPKCS7 = d2i_PKCS7_bio(receiptBIO, nil)

BIO_free(receiptBIO)

// 3

guard receiptPKCS7 != nil else {

receiptStatus = .unknownReceiptFormat

return nil

}

这段代码的工作原理:

1) 要在OpenSSL中使用envelope,首先必须将其转换为BIO,这是OpenSSL使用的抽象I / O结构。要创建一个新的BIO对象,OpenSSL需要一个指向C中原始数据字节的指针。C字节是一个Swift UInt8。由于您可以将任何Sequence和Data表示的数组初始化为UInt8序列,因此只需传入Data实例即可创建[UInt8]数组。然后,您将该数组作为原始字节指针传递。这是可能的,因为Swift隐式桥接函数参数,创建指向数组元素的指针。然后,OpenSSL调用将收据写入BIO结构。

2) 您将BIO对象转换为名为receiptPKCS7的OpenSSL PKCS7数据结构。完成后,您不再需要BIO对象并可以释放先前为其分配的内存。

3) 如果出现任何问题,那么receiptPKCS7将是一个没有指向或指向nil的指针。在这种情况下,请设置状态以反映验证失败。

接下来,您需要确保容器包含签名和数据。将以下代码添加到loadReceipt()方法的末尾以执行这些检查:

// Check that the container has a signature

guard OBJ_obj2nid(receiptPKCS7!.pointee.type) == NID_pkcs7_signed else {

receiptStatus = .invalidPKCS7Signature

return nil

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值