Apple’s receipt signing mechanism allows your application to check the validity of an in-app purchase either locally on the device or on your server.
The receipts are signed with PKCS7 to ensure that they haven’t been forged or tampered with. Therefore your client can just pass them to the server and the server can validate them and read their content.
The simplest way for your server to verify that the receipts are valid is to pass them to Apple’s API which verifies the signature validity and unpacks the ASN1 contents into a much friendlier JSON data structure.
The Receipt Verification API seems pretty simple, if a little willfully obscure, but there are some tricks to using it.
+
and /
, however no white space or other
non-encoding characters are allowed.Content-Type: application/x-www-form-urlencoded
, even
though it is not actually encoded in this way.+
and /
characters will be mangled.This is all a bit strange, and if you make any mistakes you get back one of these rather opaque error messages:
{"status":21002}
{"status":21002, "exception":"java.lang.IllegalArgumentException"}
But after much messing around, I found that the following works:
import base64
import json
import requests
def send_apple_receipt(receipt_data):
receipt_base64 = base64.b64encode(receipt_data)
receipt_json = json.dumps({"receipt-data": receipt_base64})
response = requests.request(
method='POST',
url='https://sandbox.itunes.apple.com/verifyReceipt',
headers={'Content-Type': 'application/x-www-form-urlencoded'},
data=receipt_json
)
if response.status_code == 200:
return response.json()
else:
raise SomeOtherException()
The above treats the receipt as an opaque blob to be sent to Apple, which is easy but it does seem a bit daft to use all this fancy crypto and not even look at what’s in it.
Unfortunately the APIs here are a bit weird, so we end up doing things like wrapping up out PKCS7 receipts in ASCII so that OpenSSL can unwrap them again … I’d like to find a better way.
from pyasn1.codec.der import decoder as decoder
from M2Crypto import SMIME, X509, BIO
import base64
certfile = 'AppleIncRootCertificate.cer'
def verify_apple_receipt(receipt):
x509_cert = X509.load_cert(certfile, format=X509.FORMAT_DER)
smime = SMIME.SMIME()
smime.set_x509_stack(X509.X509_Stack())
x509_store = X509.X509_Store()
x509_store.add_x509(x509_cert)
smime.set_x509_store(x509_store)
receipt_cooked = (
'-----BEGIN PKCS7-----\n' +
base64.encodestring(receipt) +
'-----END PKCS7-----\n'
)
receipt_bio = BIO.MemoryBuffer(receipt_cooked)
receipt_smime = SMIME.load_pkcs7_bio(receipt_bio)
receipt_asn1 = smime.verify(receipt_smime)
return decoder.decode(receipt_asn1)
with open('sandboxReceipt') as fh:
receipt = fh.read()
receipt_data = verify_apple_receipt(receipt)
print receipt_data
What comes out? A parsed ASN1 structure which hopefully resembles the structure in the Apple docco. On the other hand, perhaps it was easier just to pass the problem off to Apple …