Decoding Apple App Store receipts (PKCS #7, ASN.1) in Java
According to the documentation,
App Store receipts are binary files packed in a
"PKCS #7 container,
as defined by RFC 2315,
with its payload encoded using ASN.1 (Abstract Syntax Notation One),
as defined by ITU-T X.6901"
The structure of a receipt is shown in the image below.
The documentation describes how to decode a receipt in C/C++, but what if you need to do this in Java? Apparently, the authors think that no one needs to decode receipts in Java because if it is Java, then we are talking about the server side, and in this case, one can obtain a JSON-encoded receipt data from the App Store validation service. In practice, however, things are not necessary as smooth and we had to decode receipts before validating them in order to handle and restore subscriptions. This post describes how I decoded receipts in King of Thieves at ZeptoLab.
Generating Java classes from the ASN.1 module
If you are familiar with Protocol Buffers, you will easily understand this step. The documentation specifies the following ASN.1 definition of a payload:
-- this definition was taken from https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html
-- app-store-receipt.asn
ReceiptModule DEFINITIONS ::=
BEGIN
ReceiptAttribute ::= SEQUENCE {
type INTEGER,
version INTEGER,
value OCTET STRING
}
Payload ::= SET OF ReceiptAttribute
InAppAttribute ::= SEQUENCE {
type INTEGER,
version INTEGER,
value OCTET STRING
}
InAppReceipt ::= SET OF InAppAttribute
END
This definition describes the structure of a payload in a way similar to Protocol Buffers .proto
files.
Now we will use the compiler from the ASN1bean library to generate Java classes
that are able to read data structured according to the above definition:
$ asn1bean-compiler -f app-store-receipt.asn -p stincmale.sandbox.examples.decodeappleappstorereceipt.asn1
The command above generates the following Java classes in the package
stincmale
:
Payload
consists ofReceiptAttribute
s, which are designated as attributes inside a payload in the image above.ReceiptAttribute
is designated as an attribute inside a payload in the image above.InAppReceipt
is the value of aReceiptAttribute
, which is designated as an in-app purchase receipt in the image above. This value consists ofInAppAttribute
s, which are designated as attributes inside an in-app purchase receipt in the image above.InAppAttribute
is designated as an attribute inside an in-app purchase receipt in the image above.
These classes require the com.beanit:asn1bean
library,
so make sure to include it in your project.
Extracting the receipt payload
Now we have classes that can decode a payload, but before doing that we need to extract it from a receipt.
I use the Bouncy Castle
org.bouncycastle:bcpkix-jdk15on library for this purpose.
Before using it, we must make sure
BouncyCastleProvider
is added to the system:
static {
Security.addProvider(new BouncyCastleProvider());
}
With Bouncy Castle and the previously generated Payload
class extracting and decoding a payload can be done the following way
(see AppStoreReceiptUtil.decodeReceipt(byte[] receipt)
):
Payload decodeReceipt(byte[] receipt) {
Payload payload;
try {
CMSSignedData signedData = new CMSSignedData(receipt);
CMSTypedData signedContent = signedData.getSignedContent();
ByteArrayOutputStream signedDataStream = new ByteArrayOutputStream();
signedContent.write(signedDataStream);
byte[] signedDataBytes = signedDataStream.toByteArray();
payload = new Payload();
payload.decode(new ByteArrayInputStream(signedDataBytes));
} catch (CMSException | IOException e) {
throw new RuntimeException(e);
}
return payload;
}
That is it. Now we have our Payload
and can read everything there is in it.
The class AppStoreReceiptUtil
has some useful methods, e.g., the toString(Payload payload, boolean omitUnsupportedAttributes)
method
that allows converting a Payload
in a human-readable formatted text. See
AppStoreReceiptDecoderExample
for an example.
-
ITU stands for the International Telecommunication Union, ITU-T stands for the ITU Telecommunication Standardization Sector.