【StoreKit2 JWS签名验证】Code-Level Support下的X.509证书链验证之旅

前言: 最近在OC SDK上搞StoreKit2,走的是我们原来的发货流程 其中发货流程有三步,最后一步:用户支付完之后拿到成功回调,我们进行发货申请, 但是现在经常返回错误,后来使用简易Demo调试了一下发现,这个问题问题并不简单。

仔细查阅文档资料:我们失去了appStoreReceiptURL收据,这个收据在 StoreKit2 中无法得到更新(这在之后了解的WWDC2021相关视频可以查到)。

但是我们后台的发货需要依据验证收据的真实性,这就导致我们要使用StoreKit2,就必须更新我们的发货逻辑,验证新的收据。

走进Apple

官方可以给出的资料如下

苹果的签名校验示意图,视频并没有对此做出过多的解释

企业微信截图_d683a1a5-4cb3-4b1a-8ff8-21bdd5c56eac.png

wwdc21-10174 在您的服务器上管理应用内购买

在上述视频中Apple解释了如何验证签名,但是这在我们熟悉以往验证流程看起来很是模糊。

image.png

其中关于JWS具体的数据格式我会在下面解释到。


在API文档上留下的一些参数

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
extension VerificationResult where SignedType == Transaction {

    /// The raw JSON web signature for the signed value.
    public var jwsRepresentation: String { get }

    /// The data for the header component of the JWS.
    public var headerData: Data { get }

    /// The data for the payload component of the JWS.
    public var payloadData: Data { get }

    /// The data for the signature component of the JWS.
    public var signatureData: Data { get }

    /// The signature of the JWS, converted to a `CryptoKit` value.
    public var signature: P256.Signing.ECDSASignature { get }

    /// The component of the JWS that the signature is computed over.
    public var signedData: Data { get }

    /// The date the signature was generated.
    public var signedDate: Date { get }

    /// A SHA-384 hash of `AppStore.deviceVerificationID` appended after
    /// `deviceVerificationNonce` (both lowercased UUID strings).
    public var deviceVerification: Data { get }

    /// The nonce used when computing `deviceVerification`.
    /// - SeeAlso: `AppStore.deviceVerificationID`
    public var deviceVerificationNonce: UUID { get }
}

复制代码
其中在WWDC中获取到jws数据格式如下:

Base64(header) + "." + Base64(payload) + "." + sign( Base64(header) + "." + Base64(payload) )

//苹果实际返回的字符大概五千多个字符。以下篇幅有限,省略一波
eyJh--很长很长很长--bGci0.eyJ0cmFu-很长很长-kxNH0.-ewQDL-也不短-WbDXMg
复制代码
  • 大致就是 Header + Payload + Signture
  • Header = Base64(header),解析base64你会得到
header: {
    alg: 'ES256',//alg 声明知道我们使用了什么签名算法
    x5c: [ //x5c 声明中数组中的证书链
          'MIIEMDueU3...',
          'MII...‘, 'xxx...xx'  
         ]
    }

复制代码
  • Payload = Base64(payload),解析出来将会得到完整可读的json
//jwsRepresentationJWS解析后
{
    "transactionId":"1000000916922942",
    "originalTransactionId":"1000000916922942",
    "bundleId":"com.xxx.ios",
    "productId":"king.xxxx.60",
    "purchaseDate":1637723816809,
    "originalPurchaseDate":1637723816809,
    "quantity":1,
    "type":"Consumable",
    "deviceVerification":"qVh9F+9eGf9KQxxxxxxpPrfcGdlJyht775ID9ytSQCWItx",
    "deviceVerificationNonce":"a8735bcf-825f-4aeb-b99c-6f866cadc96e",
    "appAccountToken":"397711d6-61b8-bfb7-c94f-8xxxxxdb7b7",
    "inAppOwnershipType":"PURCHASED",
    "signedDate":1637723816914
}
复制代码
  • Signture是由 Base64 的 header 和 payload 签名而成,我们需要在服务器 (PS: 大概是这样(逃
    1. 使用header获取alg算法以及x5c证书用来解密signture签名(可以使用你最喜欢的密码库来解密交易信息的签名
    1. 可能需要使用CA证书来验证x5c证书链的真实性。
    1. 验证解密获获取到 signture 值之后,与 base64 解密之后 payload 进行验证。

总结一下,如果你只是想获取交易中的具体参数,你直接base64 Decode Payload参数就行了,但是如果你需要验证签名,则必须使用到Signture, Header

但是存在几个疑惑点,一直查不到相关的信息:

扫描二维码关注公众号,回复: 13471955 查看本文章
1. alg和x5c我们获取到了,但是最喜欢的密码库是什么意思?我们到底该如何解密?
2. 查阅资料发现x5c验证需要一个根证书,苹果并未提供。
3. 或许是出于推荐客户端自我验证的方式,苹果除了提供以上信息,并未透露其他服务器验证签名的信息以及示例代码。
复制代码

于是开始一边骂着时差一边联系苹果技术支持(比审核团队舒服多了

不确定使用哪一个根证书进行验证签名,于是联系Apple PKI团队: 以下是邮件内容:

2021-12-01

Dear Apple PKI team:

Good morning! I have been troubled by this problem for many days, I beg you to help me

Where can I download the Apple StoreKit 2 root certificate to install on my PHP server to verify StoreKit 2 transactions and make sure the JWS certificate chain comes from Apple?

Regrads

2021-12-03

Hello Ray,

Our certificates are located here- www.apple.com/certificate…

Apple Root G3 Apple Root CA - G3 Root.

Regards, Apple PKI

我现在相信你应该知道选择哪一个证书进行验证了
复制代码

以下是我使用Code-Level Support联系技术人员

Code-Level Support $99每年两次,看到就是赚到好吗 XDM点个赞

Send:On November 29, 2021

PLATFORM AND VERSION iOS macOS 12.0.1 Xcode 13.1 iPhone 12 mini

DESCRIPTION OF PROBLEM

Dear Apple developer team:

Hi, good afternoon~ This is My case ID: XXXX. And my question description:

Hello! Recently we encountered a problem while trying out some new features of StoreKit2. Because we used a background verification method to verify receipts (developer.apple.apple.com/documentati…), but we found that we were unable to verify the legitimacy and authenticity of the receipt. (stackoverflow.com/questions/6…), but it didn’t work, we don’t know which one certificates will actually work, so I want to know How to verify the legitimacy of the jwsReceipt in StoreKit2 on the server?

翻译: 嗨,下午好~这是我的案例ID:XXXXX。我的问题描述:

你好!最近我们在尝试StoreKit2的一些新功能时遇到了一个问题。因为我们使用了后台验证的方式来验证收据苹果内购服务器API,但是我们发现我们无法验证收据的合法性和真实性。stackoverflow,但是没用,我们不知道哪个证书会真正起作用,所以我想知道如何在服务器上验证StoreKit2中jwsReceipt的合法性?

STEPS TO REPRODUCE

  1. Start Purchase
  2. entered the AppleId Sercet
  3. completed the purchase
  4. get Transaction value, e.g:displayPrice, jwsRepresentation
  5. get what I want from jsonRepresentation
  6. but idk jwsString legitimacy or truly from Apple?
  7. How Can I know about it?

Regrads Ray.

Ray: 主要是叙述我们在使用这块功能时遇到的问题,以及我们对验证收据的迷惑性,以及相应步骤。贴出我们查阅的资料链接,有利于技术人员更加清晰的了解我们的需求,这在之后的回信中将会得到阐释。

收到 On November 30, 2021

Hello Ray,

You stated - "Recently we encountered a problem while trying out some new features of StoreKit2. Because we used a background verification method to verify receipts (developer.apple.apple.com/documentati…), but we found that we were unable to verify the legitimacy and authenticity of the receipt.

When you mention the term receipt - are you referencing the appStoreReceiptURL - or are you referencing the verification result which comes with a completed StoreKit 2 transaction. Let mejust be clear, the appStoreReceiptURL remains a part of all App Store apps, both macOS, iOS, tvOS and watchOS. On the other hand, with StoreKit 2, verifying the transaction can be managed using the checkVerified method. This method provides a much simpler method to verify the legitimacy of a transaction compared to what was provided by validating the appStoreReceipt for a StoreKit 1 transaction.

Are you trying to do more with the verification result? Please explain in more detail what you want to achieve.

翻译: 你说 - “最近我们在尝试StoreKit2的一些新功能时遇到了一个问题。因为我们使用了后台验证方法来验证收据API,但我们发现我们无法核实收据的合法性和真实性。“

当您提到术语收据时 - 您是在引用 appStoreReceiptURL - 还是在引用已完成的 StoreKit 2 交易附带的验证结果。让我明确一点,appStoreReceiptURL 仍然是所有 App Store 应用程序的一部分,包括 macOS、iOS、tvOS 和 watchOS。另一方面,在 StoreKit 2 中,可以使用 checkVerified 方法验证交易。与通过验证 StoreKit 1 交易的 appStoreReceipt 所提供的方法相比,此方法提供了一种更简单的方法来验证交易的合法性。

您是否想对验证结果做更多的事情?请更详细地说明您想要实现的目标。

Sincerely Yours,

Rich Kubota

Apple Inc

One Apple Park Way, MS 122DEF

Cupertino, CA 95014

USA

苹果告知了StoreKit2内提供了可在客户端检验收据合法性的API,但是我们需要的是服务器验证,他可能不太明白我的意思,所以我需要进一步解释我的需求

继续发送

从回复邮件来看,苹果不是太明白我的意思,这一次重新措辞,加上代码以及英文注释重新阐述我所要的。

2021-12-02

Hello Rich,

First of all, thank you for explaining the appStoreReceiptURL for me. I understand this point and it feels good to use it. At the same time, thank you very much for explaining StoreKit2. The checkVerified method is very good. But we are a game company. The high income and a lot fraudulent players make us have to use server verification. However, we have not found out how to perform server verification in the official documents and sample codes downloaded from WWDC. Below I will use the code to give a detailed example of our needs;

翻译: 首先,感谢您为我解释了 appStoreReceiptURL。这点我理解,用起来感觉还不错。同时非常感谢您对StoreKit2的解释。 checkVerified 方法非常好。但我们是一家游戏公司。高收入和大量欺诈玩家让我们不得不使用服务器验证。但是在WWDC下载的官方文档和示例代码中,我们并没有找到如何进行服务器验证。下面我会用代码来详细举例说明我们的需求;

    //completed the IAP,Waiting for purchaseResult success or Failure
    guard  let result = await purchase(toGetPurchaseResult: product) else {
        return
    }
    //When I get PurchaseResult, I dont' want `checkVerified`  method
    //I need send some paraments to my server,and which paraments that ensure the server verifies the receipt?
    switch result {

    case .success(let verificationResult):
        // POST with some params to Server, Whether to include `jwsRepresentation` or more parameters?
        // The server tells me whether this order is real
        // deliver to user
        // finish transaction
复制代码

In summary, I have three questions about StoreKit2:

  1. What parameters do I need to transmit to the server to ensure that the receipt is verified?//我需要传什么参数给服务器以确保他可以验证签名?
  2. How to verify when the server gets these receipts?//服务器该如何验证?
  3. If it contains JWS data, how should the server verify the authenticity of JWS?//如何验证jws的真实?

收到回复 <时间充裕的朋友可以好好看看>

2021-12-03 04:11

Hello Ray,

Something to understand with StoreKit2, purchased transactions are not updated to the appStoreReceipt as are StoreKit1 transactions. When the app is notified of a successful transaction - a signed transaction (receipt) is returned with the transaction indication. The best place to see a description of this is in the 2021 WWDC presentation "Manage in-app purchases on your server". A transcript is avaiable with the video. Search for the string

"Additionally, with StoreKit 2, we're introducing new signed transactions in a JWS, or JSON web signature" Based on your question below, it appears that you hve a desire to validate the signed transaction signature section. Per the presentation

" Verifying the signature is an option for you to validate that the transaction came from Apple and is trustworthy. If you only want to see the contents of the transaction, this step is not required. However, to verify the signature, you will need to use the claims available in the header portion of the signed transaction info. Use the alg claim to know what signing algorithm we used, and use the certificate chain in the array in the x5c claim. Once you have these two things, you can use your favorite cryptographic library to verify the signature of the signed transaction info."

If you are looking for sample code to manage this, I don't have such a sample. This would be an enhancement request which you can submit using the Apple Developer Feedback Assistant web page. One question which I'm looking into - " If the StoreKit2 app makes the checkVerified call, why might the app want to also validate the signature portion of the signed transaction."

翻译:

对 StoreKit2 的理解是,购买的交易不会像 StoreKit1 交易那样更新到 appStoreReceipt。当应用程序收到交易成功通知时 - 将返回带有交易指示的已签名交易(收据)。查看此描述的最佳位置是 2021 年 WWDC 演示文稿“Manage in-app purchases on your server”。视频可提供文字记录。Command+F 搜索'Transcript'

此外,对于 StoreKit 2,我们将在 JWS 或 JSON 网络签名中引入新的签名交易”根据您在下面的问题,您似乎希望验证已签名的交易签名部分。根据

根据演示“验证签名是您验证交易是否来自 Apple 并且是可信的一个选项。如果您只想查看交易的内容,则不需要此步骤。但是,要验证签名,您将需要使用签名的标题部分中可用的声明、交易信息。使用 alg 声明知道我们使用了什么签名算法,并使用 x5c 声明中数组中的证书链。一旦你有了这两件事,你就可以使用你最喜欢的密码库来验证签名交易信息的签名。

如果你正在寻找示例代码来管理这个,我没有这样的示例。这将是一个增强您可以使用 Apple 开发人员反馈助理网页提交的请求。我正在研究的一个问题 - “如果 SK2 应用程序进行 checkVerified 调用,为什么该应用程序还想验证已签名交易的签名部分

从这里,我们得知WWDC中确实有提到在Storekit2中的API调用并不会更新到appStoreReceipt,而且提醒了我们header很重要,但是我们无法理解什么是证书链。同时技术人员还表示了他的困惑,为什么要服务器进行复杂的验 证

然后过了三个小时,又收到了一封邮件

2021-12-03 04:11

Hello Ray,

I now have a better explanation between the verificationResult method and the signed Transaction (receipt) associated with every successful transaction. The verificationResult method provides information for app use as to the validity of the transaction. However, given that the app might be running under a jailbroken environment, the app process can take an additional step to prevent a jailbreak from simply indicating that the tranaction is valid - by sending the signed transaction to your server for processing of the signature. Such action takes place on your server. The server receives the signed transaction information then processes the signature portion of the signed transaction as described in the WWDC Session video which I referenced in my earlier response - "Managing In-App Purchases on your server"

However, to verify the signature, you will need to use the claims available in the header portion of the signed transaction info. Use the alg claim to know what signing algorithm we used, and use the certificate chain in the array in the x5c claim. Once you have these two things, you can use your favorite cryptographic library to verify the signature of the signed transaction info.

The above process is a serverside issue to implement. Im asked for a sampleode reference. I'm not sure whether one exists, but is so I'll send a reference if I her of one. If I learn of additional information on this subject, I'll respond. In the meantime, I suggest that you aubmit feedback asking for more information as to the algorithm possibilities and a sample code implementation.

翻译:

我现在在 verifyResult 方法和与每个成功交易相关的签名交易(收据)之间有了更好的解释。 verifyResult 方法为应用程序提供有关交易有效性的信息。然而,考虑到应用程序可能在越狱环境下运行,应用程序进程可以采取额外的步骤来防止越狱仅仅表明交易有效 - 通过将签名的交易发送到您的服务器进行签名处理。此类操作发生在您的服务器上。服务器接收已签名的交易信息,然后按照我在之前的响应中引用的 WWDC 会话视频中的描述处理已签名交易的签名部分 - “#Manage in-app purchases on your server”

但是,要验证签名,您将需要使用签名交易信息的Header部分中可用的声明。使用 alg 声明知道我们使用什么签名算法 使用,并在 x5c 声明的数组中使用证书链。一旦你有了这两件事,你就可以使用你最喜欢的密码库来验证签名交易信息的签名

上述过程是要实现的服务器端问题。我要求提供示例参考。我不确定是否存在,但如果我是她的话,我会发送一份推荐信。如果我了解到有关此主题的其他信息,我会回复。同时,我建议您提交反馈,要求提供有关算法可能性和示例代码实现的更多信息。

以上大概意思就是重复了WWDC视频的内容,但是也给了一点希望,就是可以提供相关示例代码的可能性,也点出了我们为什么要是服务器验证的问题: 防止中间人攻击篡改数据,造成资产流失。其中这个证书链很重要,后文会提出。

发送邮件

(本文发送之后,收到Apple PKI的回复,根证书在)Apple Root G3 Apple Root CA - G3 Root

2021-12-03

Dear Rich,

You stated "to verify the signature, you will need to use the claims available in the header portion of the signed transaction info. Use the alg claim to know what signing algorithm we used, and use the certificate chain in the array in the x5c claim . Once you have these two things, you can use your favorite cryptographic library to verify the signature of the signed transaction info.”

Yes, after I communicated with my colleagues in detail and watched the WWDC2021 video, we decided that we need a sample code to show me how to use the algsigning algorithm and x5ccertificate chain to verify the signature. According to my serverside colleague , this will also use the root certificate. We don’t know where to get the root certificate (if needed, please tell me where to get this certificate), if the root certificate is not needed If so, please use a complete sample code to solve my question.

If you don’t have sample code, please recommend your colleagues to tell me how to solve this problem, such as specific thinking steps and effective links on the Internet, such as: Developer Forum

Best Regards, Ray 翻译:

亲爱的 Rich Kubota,

您说“要验证签名,您需要使用签名交易信息的标头部分中可用的声明。使用 alg 声明了解我们使用的签名算法,并使用数组中的证书链“在 x5c 声明中。一旦你有了这两个东西,你就可以使用你最喜欢的加密库来验证签名交易信息的签名。"

是的,在和同事详细沟通,看了WWDC2021视频后,我们决定需要一个示例代码来演示如何使用alg签名算法和x5c证书链来验证签名。根据我的服务器端的同事,这个也会用到根证书,不知道从哪里获取根证书(如果需要,请告诉我从哪里获取这个证书),如果不需要根证书,如果是,请用完整的示例代码解决我的问题,如果你没有示例代码,请推荐你的同事告诉我如何解决这个问题,比如具体的思考步骤和网上有效的链接,比如: 开发者论坛

Ray

周五,收到两份回复

Dear Ray,

I will present your issues to may App Store Seerver contacts. Please understand that this is an informal method for handling this request. The preferred method whih I suggest that youfollow is to submit a API and documentation enhancement request that such information be provided by Apple. There is no direct sample which I'm aware of as the issue is for a server - not for a macOS, iOS or watchOS device.

To submit the API enhancement request, please use the Apple Developer Feedback Assistant web site. By submitting the feedback, you will be generating a document that is accessible to all of Apple. You comment here have limited scope in that only members of Developer Relations have access to your comments. By submitting the enhancement request, I can forward the reference company wide.

In the meantime, I will see what other details I can find. If I do so, I will pass them along.

翻译:

我会将您的问题提交给可能的 App Store Seerver 联系人。请理解这是处理此请求的非正式方法。我建议您遵循的首选方法是提交 API 和文档增强请求,由 Apple 提供此类信息。没有我所知道的直接示例,因为问题是针对服务器的 - 而不是针对 macOS、iOS 或 watchOS 设备。

要提交 API 增强请求,请使用Apple Developer Feedback Assistant web site网站。通过提交反馈,您将生成一份可供所有 Apple 用户访问的文档。您在此处发表评论的范围有限,因为只有开发者关系的成员才能访问您的评论。通过提交增强请求,我可以在整个公司范围内转发参考。

与此同时,我会看看我还能找到什么其他细节。如果我这样做,我会将它们传递下去。

Hello Ray,

I've heard back from StoreKit engineering. With regards to understanding the validation of signed transaction signatures, a good place to start is the JSON Web Tokens web site. As for Apple documentation on this subject, the team understands the need for such and there is work in process. As for when the documentation will be available, I don't have an answer. I still suggest that you submit a documentation enhncement request after reviewing the JSON Web Token site to ensure that any specific issues are addressed in Apple Developer documentation.

翻译:

我收到了 StoreKit 工程方面的回复。关于了解已签名交易签名的验证,一个不错的起点是 JSON Web Tokens web site。至于 Apple 关于此主题的文档,该团队了解对此类文档的需求,并且正在开展工作。至于文档何时可用,我没有答案。我仍然建议您在查看 JSON Web Token 站点后提交文档增强请求,以确保 Apple Developer 文档中解决了任何特定问题。

之前只是在JWT上进行数据查看,从来没想到使用他的library,之后期间使用Go,PHP进行解密和验证,一直没有得到较好的结果。

Hello Kubota,

When I use the certificate to verify the signature, I get an error: expected key of size 256 bits, but was given 384 bits. Then I found the Apple Root CA - G3 Root given by Apple PKI , is: Public-Key: (384 bit). And our alg was ES256. I don’t know if it’s the problem with my code or I was wrong from the start of the certificate, so I desperately need your help. I contacted feedbackassistant.apple.com/, but it did not help me.

翻译:

当我使用证书来验证签名时,我收到一个错误:“预期的密钥为 256 位,但得到了 384 位”。然后我找到了 Apple PKI 给出的 Apple Root CA - G3 Root,是: Public-Key: (384 bit)。我们的算法是 ES256。 不知道是我代码的问题还是我一开始就错了 证书,所以我非常需要你的帮助。 我联系了 feedbackassistant.apple.com/,但没有帮助我。

以上我表明我无法使用证书来解密我的加密字符串,请求帮助该使用什么来解密它。后来的事实证明,我使用了错误的证书进行signture解密。

Hello Ray,

I'm not a security specialist so I'm not sure how to answer your question. IT's good that you've submitted the documentation enhancement request. My current investigation is to see if I can learn of a process using commandline tools to validate the signature. If I can do this, then I'll explain the technique and you would then be able to program this to your server. This is where my efforts are now.

翻译: 我不是安全专家,所以我不确定如何回答您的问题。您已经提交了文档增强请求,这很好。我目前的调查是看我是否可以了解使用命令行工具验证签名的过程。如果我能做到这一点,那么我将解释该技术,然后您就可以将其编程到您的服务器上。这就是我现在努力的地方。

Hello Ray,

I've been provided an RFC on which we based our JSON Web Signature implmentation. It's another reference which you can make use of pending the release of documentation from the App Store Server team.

翻译: 我已经获得了一个 RFC,我们的 JSON Web 签名实现基于它。这是另一个参考,您可以在等待 App Store Server 团队发布文档时使用。

RFC 7515

Sincerely Yours,

其中RFC 7515中写道:

4.1.6. "x5c" (X.509 Certificate Chain) Header Parameter

The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain RFC5280 corresponding to the key used to digitally sign the JWS. The certificate or certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (Section 4 of RFC4648 -- not base64url-encoded) DER ITU.X690.2008 PKIX certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. The recipient MUST validate the certificate chain according to RFC5280 and consider the certificate or certificate chain to be invalid if any validation failure occurs.

“x5c”(X.509 证书链)头参数包含 X.509 公钥证书或证书链 RFC5280,对应于用于对 JWS 进行数字签名的密钥。证书或证书链表示为证书值字符串的 JSON 数组。数组中的每个字符串都是 base64 编码(RFC4648 的第 4 节——不是base64url 编码)获得的DER ITU.X690.2008 PKIX 证书值。包含与用于对 JWS 进行数字签名的密钥相对应的公钥的证书必须是第一个证书。这可以跟随着额外的证书,每个后续的证书都是用来证明前一个证书的。接收方必须根据RFC5280验证证书链,如果发生任何验证失败,则认为该证书或证书链无效。此标题参数的使用是可选的。

以下是我使用PHP解析JWS字符串的源码

<?php                 //php8.0环境
use Firebase\JWT\JWT; //在jwt.io上找到一个库用起来很快,这里自行百度php composer
use Firebase\JWT\Key; //composer require firebase/php-jwt
require __DIR__ . '/vendor/autoload.php';
error_reporting(E_ALL ^ E_WARNING ^ E_NOTICE);
$components = explode('.', $jws);
$header = base64_decode($components[0]);
$payload = base64_decode($components[1]);
$signature = base64_decode($components[2]);

$headerJson = json_decode($header, true);
$algorithm = $headerJson['alg'];
$x5cArray = $headerJson['x5c'];
$certificate = '-----BEGIN CERTIFICATE-----' . PHP_EOL;
$certificate .= chunk_split($headerJson['x5c'][0], 64, PHP_EOL);
$certificate .= '-----END CERTIFICATE-----' . PHP_EOL;
$pkey_object = openssl_pkey_get_public($certificate);
$pkey_array = openssl_pkey_get_details($pkey_object);
$publicKey = $pkey_array['key'];
//传入jws以及公钥匙以及加密算法
$decoded = JWT::decode($jws, new Key($publicKey, $algorithm));
复制代码

这里主要做了这样几个事情:

  1. 获取JWS内的三段字符串
  2. 对Header进行json序列化,获取其算法以及x5c证书链。
  3. 通过x5c证书数组获取其第一个参数,通过openssl命令生成公钥证书。
  4. 通过Firebase的JWT库传入jws、alg、public key进行解析。

到这里解密算是结束了,但是有以下几个点比较疑惑:

  • 我们从x5c获取的公钥能否保证真实不被篡改?上方代码无法保证。
  • 既然公钥不可信,解密出来的数据当然也不可信。
  • 截取你的数据,修改你的数据,用中间人私钥加密篡改的数据,再生成新的公钥数组发送给你。
  • 按照目前流程,你会相信这是一份真实的签名后的信息。

以上,我们需要验证公钥的真实性,然后通过验证后的公钥解密数据,然后和decoded Payload数据做比对,或者和客户端数据做比对。在代码上方的文字中我提到:

这或许就是验证收据签名的真实性的最后一步了吧;

话不多说,上代码:

<?php                  //php版本 >= 7.4
use Firebase\JWT\JWT;
use Firebase\JWT\Key; //composer require firebase/php-jwt
require __DIR__ . '/vendor/autoload.php';
error_reporting(E_ALL ^ E_WARNING ^ E_NOTICE);
$jws = 'eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTURDQ0E3YWdBd0lCQWdJUWFQb1BsZHZwU29FSDBsQnJqRFB2OWpBS0JnZ3Foa2pPUFFRREF6QjFNVVF3UWdZRFZRUURERHRCY0hCc1pTQlhiM0pzWkhkcFpHVWdSR1YyWld4dmNHVnlJRkpsYkdGMGFXOXVjeUJEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURUxNQWtHQTFVRUN3d0NSell4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJeE1EZ3lOVEF5TlRBek5Gb1hEVEl6TURreU5EQXlOVEF6TTFvd2daSXhRREErQmdOVkJBTU1OMUJ5YjJRZ1JVTkRJRTFoWXlCQmNIQWdVM1J2Y21VZ1lXNWtJR2xVZFc1bGN5QlRkRzl5WlNCU1pXTmxhWEIwSUZOcFoyNXBibWN4TERBcUJnTlZCQXNNSTBGd2NHeGxJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnVW1Wc1lYUnBiMjV6TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCT29UY2FQY3BlaXBOTDllUTA2dEN1N3BVY3dkQ1hkTjh2R3FhVWpkNThaOHRMeGlVQzBkQmVBK2V1TVlnZ2gxLzVpQWsrRk14VUZtQTJhMXI0YUNaOFNqZ2dJSU1JSUNCREFNQmdOVkhSTUJBZjhFQWpBQU1COEdBMVVkSXdRWU1CYUFGRDh2bENOUjAxREptaWc5N2JCODVjK2xrR0taTUhBR0NDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnZiUzkzZDJSeVp6WXVaR1Z5TURFR0NDc0dBUVVGQnpBQmhpVm9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMWGQzWkhKbk5qQXlNSUlCSGdZRFZSMGdCSUlCRlRDQ0FSRXdnZ0VOQmdvcWhraUc5Mk5rQlFZQk1JSCtNSUhEQmdnckJnRUZCUWNDQWpDQnRneUJzMUpsYkdsaGJtTmxJRzl1SUhSb2FYTWdZMlZ5ZEdsbWFXTmhkR1VnWW5rZ1lXNTVJSEJoY25SNUlHRnpjM1Z0WlhNZ1lXTmpaWEIwWVc1alpTQnZaaUIwYUdVZ2RHaGxiaUJoY0hCc2FXTmhZbXhsSUhOMFlXNWtZWEprSUhSbGNtMXpJR0Z1WkNCamIyNWthWFJwYjI1eklHOW1JSFZ6WlN3Z1kyVnlkR2xtYVdOaGRHVWdjRzlzYVdONUlHRnVaQ0JqWlhKMGFXWnBZMkYwYVc5dUlIQnlZV04wYVdObElITjBZWFJsYldWdWRITXVNRFlHQ0NzR0FRVUZCd0lCRmlwb2RIUndPaTh2ZDNkM0xtRndjR3hsTG1OdmJTOWpaWEowYVdacFkyRjBaV0YxZEdodmNtbDBlUzh3SFFZRFZSME9CQllFRkNPQ21NQnEvLzFMNWltdlZtcVgxb0NZZXFyTU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3Tm9BREJsQWpFQWw0SkI5R0pIaXhQMm51aWJ5VTFrM3dyaTVwc0dJeFBNRTA1c0ZLcTdoUXV6dmJleUJ1ODJGb3p6eG1ienBvZ29BakJMU0ZsMGRaV0lZbDJlalBWK0RpNWZCbktQdThteW1CUXRvRS9IMmJFUzBxQXM4Yk51ZVUzQ0JqamgxbHduRHNJPSIsIk1JSURGakNDQXB5Z0F3SUJBZ0lVSXNHaFJ3cDBjMm52VTRZU3ljYWZQVGp6Yk5jd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NakV3TXpFM01qQXpOekV3V2hjTk16WXdNekU1TURBd01EQXdXakIxTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVMTUFrR0ExVUVDd3dDUnpZeEV6QVJCZ05WQkFvTUNrRndjR3hsSUVsdVl5NHhDekFKQmdOVkJBWVRBbFZUTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVic1FLQzk0UHJsV21aWG5YZ3R4emRWSkw4VDBTR1luZ0RSR3BuZ24zTjZQVDhKTUViN0ZEaTRiQm1QaENuWjMvc3E2UEYvY0djS1hXc0w1dk90ZVJoeUo0NXgzQVNQN2NPQithYW85MGZjcHhTdi9FWkZibmlBYk5nWkdoSWhwSW80SDZNSUgzTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0h3WURWUjBqQkJnd0ZvQVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3UmdZSUt3WUJCUVVIQVFFRU9qQTRNRFlHQ0NzR0FRVUZCekFCaGlwb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxXRndjR3hsY205dmRHTmhaek13TndZRFZSMGZCREF3TGpBc29DcWdLSVltYUhSMGNEb3ZMMk55YkM1aGNIQnNaUzVqYjIwdllYQndiR1Z5YjI5MFkyRm5NeTVqY213d0hRWURWUjBPQkJZRUZEOHZsQ05SMDFESm1pZzk3YkI4NWMrbGtHS1pNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQkFYaFNxNUl5S29nTUNQdHc0OTBCYUI2NzdDYUVHSlh1ZlFCL0VxWkdkNkNTamlDdE9udU1UYlhWWG14eGN4ZmtDTVFEVFNQeGFyWlh2TnJreFUzVGtVTUkzM3l6dkZWVlJUNHd4V0pDOTk0T3NkY1o0K1JHTnNZRHlSNWdtZHIwbkRHZz0iLCJNSUlDUXpDQ0FjbWdBd0lCQWdJSUxjWDhpTkxGUzVVd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF3TkRNd01UZ3hPVEEyV2hjTk16a3dORE13TVRneE9UQTJXakJuTVJzd0dRWURWUVFEREJKQmNIQnNaU0JTYjI5MElFTkJJQzBnUnpNeEpqQWtCZ05WQkFzTUhVRndjR3hsSUVObGNuUnBabWxqWVhScGIyNGdRWFYwYUc5eWFYUjVNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVFzd0NRWURWUVFHRXdKVlV6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkpqcEx6MUFjcVR0a3lKeWdSTWMzUkNWOGNXalRuSGNGQmJaRHVXbUJTcDNaSHRmVGpqVHV4eEV0WC8xSDdZeVlsM0o2WVJiVHpCUEVWb0EvVmhZREtYMUR5eE5CMGNUZGRxWGw1ZHZNVnp0SzUxN0lEdll1VlRaWHBta09sRUtNYU5DTUVBd0hRWURWUjBPQkJZRUZMdXczcUZZTTRpYXBJcVozcjY5NjYvYXl5U3JNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01RQ0Q2Y0hFRmw0YVhUUVkyZTN2OUd3T0FFWkx1Tit5UmhIRkQvM21lb3locG12T3dnUFVuUFdUeG5TNGF0K3FJeFVDTUcxbWloREsxQTNVVDgyTlF6NjBpbU9sTTI3amJkb1h0MlFmeUZNbStZaGlkRGtMRjF2TFVhZ002QmdENTZLeUtBPT0iXX0.eyJ0cmFuc2FjdGlvbklkIjoiMTAwMDAwMDkxNjkyMjk0MiIsIm9yaWdpbmFsVHJhbnNhY3Rpb25JZCI6IjEwMDAwMDA5MTY5MjI5NDIiLCJidW5kbGVJZCI6ImNvbS5qcC5oaW1lLmlvcyIsInByb2R1Y3RJZCI6ImtpbmcudGVzdC5nb2xkLjYwIiwicHVyY2hhc2VEYXRlIjoxNjM3NzIzODE2ODA5LCJvcmlnaW5hbFB1cmNoYXNlRGF0ZSI6MTYzNzcyMzgxNjgwOSwicXVhbnRpdHkiOjEsInR5cGUiOiJDb25zdW1hYmxlIiwiZGV2aWNlVmVyaWZpY2F0aW9uIjoicVZoOUYrOWVHZjlLUWgrQjNmSUFvUUtMOEt6MENrbVZHZlVpd3BQcmZjR2RsSnlodDc3NUlEOXl0U1FDV0l0eCIsImRldmljZVZlcmlmaWNhdGlvbk5vbmNlIjoiYTg3MzViY2YtODI1Zi00YWViLWI5OWMtNmY4NjZjYWRjOTZlIiwiYXBwQWNjb3VudFRva2VuIjoiMzk3NzExZDYtNjFiOC1iZmI3LWM5NGYtOGVhNjcwZmRiN2I3IiwiaW5BcHBPd25lcnNoaXBUeXBlIjoiUFVSQ0hBU0VEIiwic2lnbmVkRGF0ZSI6MTYzNzcyMzgxNjkxNH0.-ewQD6FbwdY_ycMHISNY7rp6VesmoJH_IURsX18JAVbb49CqUnjXHzxMHwTv_Pgs59DUIsUY1rt8cQWLWbDXMg';
$components = explode('.', $jws);
$header = base64_decode($components[0]);
$payload = base64_decode($components[1]);
$signature = base64_decode($components[2]);

$headerJson = json_decode($header, true);
$algorithm = $headerJson['alg'];
$x5cArray = $headerJson['x5c'];

foreach ($x5cArray as $X5C) {
    $certificate = '-----BEGIN CERTIFICATE-----' . PHP_EOL;
    $certificate .= chunk_split($X5C, 64, PHP_EOL);
    $certificate .= '-----END CERTIFICATE-----' . PHP_EOL;
    $certificates[] = openssl_x509_read($certificate);//OpenSSLCertificate
}
//验证证书链的有效性(每一个都由下一个签名,根证书除外),倒序,从证书链的最后一个开始:
$applePemString = file_get_contents('/Users/Ray/PhpstormProjects/jws/AppleRootCA-G3.pem');
$applePem = openssl_x509_read($applePemString);
/*
 *  * @param OpenSSLCertificate|string|resource $certificate
    * @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $public_key
    * @return int Returns 1 if the signature is correct, 0 if it is incorrect, and -1 on error.
 */
//首先用根证书验证x5c证书链的最后一个证书
$nextCode = openssl_x509_verify($certificates[0], $certificates[1]);
//Returns 1 if the signature is correct, 0 if it is incorrect, and -1 on error.
printf("第一个证书给第二证书验证结果为:%s\n",$nextCode);
if ($nextCode == 1) {
    $finalCode = openssl_x509_verify($certificates[1], $certificates[2]);
    printf("第二个证书给第三个证书验证结果为:%s\n",$finalCode);
    //如果验证正确的话,则用第一个证书验证倒数第二个证书
    if ($finalCode == 1) {
        $code = openssl_x509_verify($certificates[2],$applePem);
        printf("根验证结果为:%s\n",$code);
        if ($code == 1) {
            //第一个证书是签署jws的证书
            $pkey_object = openssl_pkey_get_public($certificates[0]);
            $pkey_array = openssl_pkey_get_details($pkey_object);
            $publicKey = $pkey_array['key'];
            //传入jws以及公钥匙以及加密算法
            $decoded = JWT::decode($jws, new Key($publicKey, $algorithm));
            //序列化解密后参数
            $decoded_array = (array) $decoded;
            echo "解密后的参数:\n" . print_r($decoded_array, true) . "\n";
        }

    }
}
/*
第一个证书给第二证书验证结果为:1
第二个证书给第三个证书验证结果为:1
根验证结果为:1
解密后的参数:
Array
(
    [transactionId] => 1000000916922942
    [originalTransactionId] => 1000000916922942
    [bundleId] => com.jp.hime.ios
    [productId] => king.test.gold.60
    [purchaseDate] => 1637723816809
    [originalPurchaseDate] => 1637723816809
    [quantity] => 1
    [type] => Consumable
    [deviceVerification] => qVh9F+9eGf9KQh+B3fIAoQKL8Kz0CkmVGfUiwpPrfcGdlJyht775ID9ytSQCWItx
    [deviceVerificationNonce] => a8735bcf-825f-4aeb-b99c-6f866cadc96e
    [appAccountToken] => 397711d6-61b8-bfb7-c94f-8ea670fdb7b7
    [inAppOwnershipType] => PURCHASED
    [signedDate] => 1637723816914
)
*/
复制代码

推荐一篇: stackoverflow: 我在x509中验证哪些字段以证明证书的合法性?

Apple PKI提供的G3证书

以上,谢谢观看。

以下只做记录,不做解读与跟进

Hello Rich,

I am happy to share a good news, I decrypted the JWS string as I wish, but it is so simple, it seems that it has nothing to do with the X.509 certificate chain. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. I benefit from this sentence, but This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. This sentence makes me very puzzled. I used PHP to verify the signature. I now compare the decrypted parameters with the parameters I sent to the server to verify the validity of the receipt. I don't know if any better way to verify, please let me know if you have a better way. Below is my code, I hope you can help others.

Hello Ray,

Thank you very much for the script sample. If you don't mind, I will anonymize the strings that are specific to you and your company. Than do I have your permission to distribute the script when asked - as a sample?

猜你喜欢

转载自juejin.im/post/7039622474274701348