Skip to content

Instantly share code, notes, and snippets.

@kikeenrique
Forked from jeiting/verify_apple_receipt.py
Last active December 28, 2019 22:04
Show Gist options
  • Save kikeenrique/7f033055ab49040f8b3bcd6ebfa8e733 to your computer and use it in GitHub Desktop.
Save kikeenrique/7f033055ab49040f8b3bcd6ebfa8e733 to your computer and use it in GitHub Desktop.
# https://medium.com/revenuecat-blog/dissecting-an-app-store-receipt-b1e9c5136482
# 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment