Verifying JWS for StoreKit 2 in-app purchase

Taiga ASANO
MIXI DEVELOPERS
Published in
5 min readMay 12, 2022

--

Monster Strike started the transition to App Store Server Notifications Version 2 in January 2022 and completed it in March 2022. I discovered the following through this project:

・A Ruby implementation of the JWT standard, ruby-jwt, could be used to verify JWS

・There is a delay for when settings are updated in App Store Connect

So I will explain in more detail with actual code in this article.

Table of Contents

· What Are App Store Server Notifications?
· Why Upgrade?
· Changes in Version 2
· Validating the JWS Provided by Apple
· Verifying with ruby-jwt
· Supporting Version 2
· Post-setup Mixed Version Notifications
· Summary

What Are App Store Server Notifications?

App Store Server Notifications notify the server of events such as refunds and subscription renewals of in-app purchases. The official StoreKit documentation recommends receiving and acting on refund notifications.

Why Upgrade?

With Apple’s release of StoreKit 2 in October 2021, server notifications received an update in the form of App Store Server Notifications Version 2.

Monster Strike was already using Version 1 for notifications, but with the likely increase in notification types supported in Version 2 and to release features that make use of App Store Server Notifications, we made the decision to jump to Version 2.

Changes in Version 2

App Store Server Notifications Version 1 sent a JSON payload if a specified endpoint was set. Version 2 sends a JSON payload encoded with JWS (JSON Web Signature). StoreKit 2 also uses JWS for receipts.

{"signedPayload": "eyJ..."}

We can just verify this, but the verification method had its own quirks that offered different challenges from OpenID Connect and other methods that often use JWT (JSON Web Tokens).

Validating the JWS Provided by Apple

signedPayload may seem like a normal JWS. However, if you read its documentation, you’ll find that Apple does not provide public keys.

In the case of JWT, etc., it is common to use JWK (JSON Web Key), etc. to publish the public key, and obtain the token using the kid field, etc. as a base. However, the only headers included in the signedPayload are alg and x5c. In addition, alg contains ES256, indicating that a public key using the ECDSA method is required.

In looking into the x5c field, I have found that RFC7515 contains a Base64-encoded X.509 certificate or chain, which can be used as a key for JWS.

The x5c field of the JWS payload provided by Apple contained three certificates. They became DER files when decoded and confirmed that the chain was as follows.

  1. Issuer: CN = Apple Worldwide Developer Relations Certification Authority, OU = G6, O = Apple Inc., C = US / Subject: CN = Prod ECC Mac App Store and iTunes Store Receipt Signing, OU = Apple Worldwide Developer Relations, O = Apple Inc., C = US
  2. Issuer: CN = Apple Root CA - G3, OU = Apple Certification Authority, O = Apple Inc., C = US / Subject: CN = Apple Worldwide Developer Relations Certification Authority, OU = G6, O = Apple Inc., C = US
  3. Issuer: CN = Apple Root CA - G3, OU = Apple Certification Authority, O = Apple Inc., C = US / Subject: CN = Apple Root CA - G3, OU = Apple Certification Authority, O = Apple Inc., C = US

Since the signature algorithm is ecdsa-with-SHA384, the public key obtained here can be used to verify JWS in ECDSA format. Among the root certificates provided by Apple PKI, only G3 is currently an ECDSA type, so we know that we can use the G3 root certificate to validate the certificate chain.

Please note that this information on JWS verification is current as of March 2022. Please refer to Apple’s documentation before verification, as it may be subject to change due to updates from Apple or certificate rotations in the future.

Verifying with ruby-jwt

Since Monster Strike uses Ruby, we used ruby-jwt, a JOSE-related library commonly used in Ruby, for verification.

Not many places use x5c field verification yet and ruby-jwt has not released support for it yet. (PRs discussed, PRs supported)

Since there is no released verification method yet, we pre-verified it and extracted the public key for verification. The following code can be used for verification and retrieving the payload.

This code takes advantage of the following functions:

∙ ruby-jwt can pass a block to detect the public key at the end of JWT.decode

∙ OpenSSL::X509::Certificate::Store can verify authenticity by passing (certificate, [array of chain]) in #verify.

The decoded payload includes information about the receipt called signedTransactionInfo, which can also be verified by code.

Supporting Version 2

The endpoint and the version can be selected from the App Store Connect settings screen. By selecting Version 2 here, you can receive Version 2 payloads.

In addition, apps that can support StoreKit 2 can validate notifications from the app to the sandbox by setting up a sandbox endpoint.

Post-setup Mixed Version Notifications

After Monster Strike was transitioned to Version 2, the server received a combination of Version 1 and Version 2 notifications for several hours.

In other words, for an environment with App Store Server Notifications Version 1, the new endpoint (or one that has already been configured) may still receive Version 1 payloads after setup. Therefore, the lowest-impact release method is to leave the notification endpoints available for both Version 1 and Version 2, and switch only the version.

After we switched from Version 1 to Version 2, we received a mixture of Version 1 and Version 2 payloads for about 8 hour.

Summary

App Store Server Notifications Version 2 now uses JWS, making it easier to verify the authenticity of payloads. Also, I think the new version allows us to get more information with its additional notification types.

On the other hand, it is necessary to check if the library you are using supports the x5c header or if it provides a mechanism to obtain the public key from the outside, and if not, you need to implement it on your own.

I hope this will be helpful to developers implementing App Store Server Notifications and supporting StoreKit 2 in the future.

Special thanks to our English team for their awesome support.

--

--