Skip to content

Instantly share code, notes, and snippets.

@jeiting
Created January 16, 2018 23:40
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save jeiting/84b724b2d9438cbb41227e144e5c3615 to your computer and use it in GitHub Desktop.
Save jeiting/84b724b2d9438cbb41227e144e5c3615 to your computer and use it in GitHub Desktop.
# Load the contents of the receipt file
receipt_file = open('./receipt_data.bin', 'rb').read()
# Use asn1crypto's cms definitions to parse the PKCS#7 format
from asn1crypto.cms import ContentInfo
pkcs_container = ContentInfo.load(receipt_file)
# Extract the certificates, signature, and receipt_data
certificates = pkcs_container['content']['certificates']
signer_info = pkcs_container['content']['signer_infos'][0]
receipt_data = pkcs_container['content']['encap_content_info']['content']
from OpenSSL.crypto import load_certificate, FILETYPE_ASN1, FILETYPE_PEM
# Pull out and parse the X.509 certificates included in the receipt
itunes_cert_data = certificates[0].chosen.dump()
itunes_cert = load_certificate(FILETYPE_ASN1, itunes_cert_data)
itunes_cert_signature = certificates[0].chosen.signature
wwdr_cert_data = certificates[1].chosen.dump()
wwdr_cert = load_certificate(FILETYPE_ASN1, wwdr_cert_data)
wwdr_cert_signature = certificates[1].chosen.signature
untrusted_root_data = certificates[2].chosen.dump()
untrusted_root = load_certificate(FILETYPE_ASN1, untrusted_root_data)
untrusted_root_signature = certificates[2].chosen.signature
import urllib.request
trusted_root_data = urllib.request.urlopen("https://www.apple.com/appleca/AppleIncRootCertificate.cer").read()
trusted_root = load_certificate(FILETYPE_ASN1, trusted_root_data)
from OpenSSL.crypto import X509Store, X509StoreContext, X509StoreContextError
trusted_store = X509Store()
trusted_store.add_cert(trusted_root)
try:
X509StoreContext(trusted_store, wwdr_cert).verify_certificate()
trusted_store.add_cert(wwdr_cert)
except X509StoreContextError as e:
print("WWDR certificate invalid")
exit()
try:
X509StoreContext(trusted_store, itunes_cert).verify_certificate()
except X509StoreContextError as e:
print("iTunes certificate invalid")
exit()
from OpenSSL.crypto import verify
try:
verify(itunes_cert, signer_info['signature'].native, receipt_data.native, 'sha1')
print("The receipt data signature is valid.")
except Exception as e:
print("The receipt data is invalid: %s" % e)
exit()
from asn1crypto.core import Any, Integer, ObjectIdentifier, OctetString, Sequence, SetOf, UTF8String, IA5String
attribute_types = [
(2, 'bundle_id', UTF8String),
(3, 'application_version', UTF8String) ,
(4, 'opaque_value', None),
(5, 'sha1_hash', None),
(12, 'creation_date', IA5String),
(17, 'in_app', OctetString),
(19, 'original_application_version', UTF8String),
(21, 'expiration_date', IA5String)
]
class ReceiptAttributeType(Integer):
_map = {type_code: name for type_code, name, _ in attribute_types}
class ReceiptAttribute(Sequence):
_fields = [
('type', ReceiptAttributeType),
('version', Integer),
('value', OctetString)
]
class Receipt(SetOf):
_child_spec = ReceiptAttribute
receipt = Receipt.load(receipt_data.native)
receipt_attributes = {}
attribute_types_to_class = {name: type_class for _, name, type_class in attribute_types}
in_apps = []
for attr in receipt:
attr_type = attr['type'].native
# Just store the in_apps for now
if attr_type == 'in_app':
in_apps.append(attr['value'])
continue
if attr_type in attribute_types_to_class:
if attribute_types_to_class[attr_type] is not None:
receipt_attributes[attr_type] = attribute_types_to_class[attr_type].load(attr['value'].native).native
else:
receipt_attributes[attr_type] = attr['value'].native
in_app_attribute_types = {
(1701, 'quantity', Integer),
(1702, 'product_id', UTF8String),
(1703, 'transaction_id', UTF8String),
(1705, 'original_transaction_id', UTF8String),
(1704, 'purchase_date', IA5String),
(1706, 'original_purchase_date', IA5String),
(1708, 'expires_date', IA5String),
(1719, 'is_in_intro_offer_period', Integer),
(1712, 'cancellation_date', IA5String),
(1711, 'web_order_line_item_id', Integer)
}
class InAppAttributeType(Integer):
_map = {type_code: name for (type_code, name, _) in in_app_attribute_types}
class InAppAttribute(Sequence):
_fields = [
('type', InAppAttributeType),
('version', Integer),
('value', OctetString)
]
class InAppPayload(SetOf):
_child_spec = InAppAttribute
in_app_attribute_types_to_class = {name: type_class for _, name, type_class in in_app_attribute_types}
in_apps_parsed = []
for in_app_data in in_apps:
in_app = {}
for attr in InAppPayload.load(in_app_data.native):
attr_type = attr['type'].native
if attr_type in in_app_attribute_types_to_class:
in_app[attr_type] = in_app_attribute_types_to_class[attr_type].load(attr['value'].native).native
in_apps_parsed.append(in_app)
receipt_attributes['in_app'] = in_apps_parsed
from pprint import pprint as pp
pp(receipt_attributes)
@kgoess
Copy link

kgoess commented Mar 26, 2018

Interesting, running this on any receipt under /Applications, or receipts generated by a mobile app, only gives me this exception

ValueError: Error parsing asn1crypto.cms.ContentInfo - class should have been universal, but application was found

@ivodvb
Copy link

ivodvb commented May 31, 2018

@kgoess you need to base64 decode the file before passing it onto ContentInfo.load

import base64
receipt_file_raw = base64.b64decode(receipt_file)

# Use asn1crypto's cms definitions to parse the PKCS#7 format
pkcs_container = ContentInfo.load(receipt_file_raw)

@liam-pat
Copy link

@jeiting thx. can decrypt receipt-data by python3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment