-
-
Save kikeenrique/7f033055ab49040f8b3bcd6ebfa8e733 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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