Poking Around in Mac App Store Receipts

published 19 Mar 2013

Yngve Åström asked if anyone knew how to tell which Apple ID installed an app store app on the MacEnterprise mailing list:

Is it just me or have anyone been able to read something useful out of the app/Contents/_MASReceipt/receipt? I’m looking for a way to find out which account was used to by an app, the _MASreceipt library looked like the right place to look. Doesn’t matter how I trie to read the receipt all I get is bits of readable info that makes very little sense to me. Looks like binary peaces of certs but nothing close to an AppStore account. The /Users/myuser/Library/Preferences/com.apple.storeagent.plist AppleID will tell me what account I’m using now but that’s it. Where can I find which account was used to by a particular app? In this case Server.app… Is it even possible to find out?

Let’s poke around and see what’s inside those receipt files the Mac App Store puts inside every app bundle. The receipt file itself is a PKCS #7 container, as defined by RFC2315, with its payload encoded using ASN.1. We can look at its certificates using openssl’s pkcs7 command:

$ openssl pkcs7 -inform der -in /Applications/Xcode.app/Contents/_MASReceipt/receipt -print_certs -text
        Version: 3 (0x2)
        Serial Number:
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
            Not Before: Nov 11 21:58:01 2010 GMT
            Not After : Nov 11 21:58:01 2015 GMT
        Subject: CN=Mac App Store Receipt Signing, OU=Apple Worldwide Developer Relations, O=Apple Inc., C=US

You’ll see that it contains a certificate chain with Apple’s root CA, the developer CA, and the receipt signing certificate. There’s a signed payload in the receipt as well, which we can see if we dump the ASN.1 data using openssl’s asn1parse command:

$ openssl asn1parse -inform der -in /Applications/Xcode.app/Contents/_MASReceipt/receipt
    0:d=0  hl=4 l=4701 cons: SEQUENCE          
    4:d=1  hl=2 l=   9 prim: OBJECT            :pkcs7-signedData
   15:d=1  hl=4 l=4686 cons: cont [ 0 ]        
   19:d=2  hl=4 l=4682 cons: SEQUENCE          
   23:d=3  hl=2 l=   1 prim: INTEGER           :01
   26:d=3  hl=2 l=  11 cons: SET               
   28:d=4  hl=2 l=   9 cons: SEQUENCE          
   30:d=5  hl=2 l=   5 prim: OBJECT            :sha1
   37:d=5  hl=2 l=   0 prim: NULL              
   39:d=3  hl=4 l= 526 cons: SEQUENCE          
   43:d=4  hl=2 l=   9 prim: OBJECT            :pkcs7-data
   54:d=4  hl=4 l= 511 cons: cont [ 0 ]        
   58:d=5  hl=4 l= 507 prim: OCTET STRING      [HEX DUMP]:318201F7300A0201120201
  569:d=3  hl=4 l=3669 cons: cont [ 0 ]        
  573:d=4  hl=4 l=1387 cons: SEQUENCE          
  577:d=5  hl=4 l=1107 cons: SEQUENCE          
  581:d=6  hl=2 l=   3 cons: cont [ 0 ]        
  583:d=7  hl=2 l=   1 prim: INTEGER           :02
  586:d=6  hl=2 l=   8 prim: INTEGER           :1859432172749CFC
  596:d=6  hl=2 l=  13 cons: SEQUENCE          
  598:d=7  hl=2 l=   9 prim: OBJECT            :sha1WithRSAEncryption

The first big blob of hex is a payload signed with sha1. This payload is also encoded using ASN.1, so let’s decode it and save it as a separate file, payload.asn:

$ openssl asn1parse -inform der -in /Applications/Xcode.app/Contents/_MASReceipt/receipt | grep -m 1 'OCTET STRING' | cut -d: -f4 | xxd -r -p > payload.asn

With it saved to disk let’s see what asn1parse has to say about it:

$ openssl asn1parse -inform der -in payload.asn
    0:d=0  hl=4 l= 503 cons: SET               
    4:d=1  hl=2 l=  10 cons: SEQUENCE          
    6:d=2  hl=2 l=   1 prim: INTEGER           :12
    9:d=2  hl=2 l=   1 prim: INTEGER           :01
   12:d=2  hl=2 l=   2 prim: OCTET STRING      [HEX DUMP]:1600
   16:d=1  hl=2 l=  10 cons: SEQUENCE          
   18:d=2  hl=2 l=   1 prim: INTEGER           :13
   21:d=2  hl=2 l=   1 prim: INTEGER           :01
   24:d=2  hl=2 l=   2 prim: OCTET STRING      [HEX DUMP]:0C00
   28:d=1  hl=2 l=  11 cons: SEQUENCE          
   30:d=2  hl=2 l=   1 prim: INTEGER           :0E
   33:d=2  hl=2 l=   1 prim: INTEGER           :01
   36:d=2  hl=2 l=   3 prim: OCTET STRING      [HEX DUMP]:020101

We can see that the payload is composed of a set of attributes, defined by two integers and an octet string. The first integer is the attribute type, the second its version (so far always 1), and the octet string its value. How the octet string is interpreted depends on the attribute type, and Apple has reserved most for private use, but a few are public:

Type Definition Value Interpretation
2Bundle identifierUTF8STRING.
3Application versionUTF8STRING.
4Opaque valueA series of bytes.
5SHA-1 hash20-byte SHA-1 digest value.
17In-app purchase receiptFurther down the ASN.1 rabbit hole.

Bundle ID and app version are self explanatory, and the SHA-1 hash is used to verify that the app bundle hasn’t been modified. IAPs have their own receipt following the same principle as the main receipt, leaving only the mysteriously named “Opaque value”:

  114:d=1  hl=2 l=  14 cons: SEQUENCE          
  116:d=2  hl=2 l=   1 prim: INTEGER           :04
  119:d=2  hl=2 l=   1 prim: INTEGER           :01
  122:d=2  hl=2 l=   6 prim: OCTET STRING      [HEX DUMP]:02043D840EE2

From Apple’s documentation we can see that it’s used together with the computer’s GUID in computing the verification hash. The ASN.1 specification tells us that it’s an integer (0x02) and that it’s four bytes long (0x04). Let’s print it as a decimal integer:

$ printf "%d\n" 0x$(openssl asn1parse -inform der -in payload.asn | grep -A 2 ':04$' | tail -1 | cut -d: -f4 | cut -c5-)

Still pretty opaque. Haven’t I seen that integer somewhere else though? Indeed I have:

$ defaults read com.apple.storeagent
    AccountKind = 0;
    AccountURLBagType = production;
    AppleID = "magervalp@mac.com";
    CreditDisplayString = "";
    DSPersonID = 1032064738;
    DownloadLocation = "/Users/pelle/Library/Application Support/AppStore";
    EligibilityCheckDate = "2013-03-18 22:26:53 +0000";
    ISBadgeValues =     {
    ISLastUpdatesQueueCheck = "2013-03-15 06:17:25 +0000";
    KnownAccounts =     (
            AccountKind = 0;
            AccountURLBagType = production;
            CreditDisplayString = "";
            DSPersonID = 1032064738;
    LastAuthTime = "2013-03-14 21:18:37 +0000";
    LastSynchedStoreFront = "143456,12";
    PurchasesInflight = 0;
    Storefront = "143456-17,13";
    UserNotificationDate = "2013-03-15 06:45:07 +0000";

Bingo - it’s the numeric app store user ID tied to my Apple ID. While we can’t directly determine which Apple ID installed a certain app, we can build a list of DSPersonIDs and their corresponding Apple IDs and get it that way.