Friday, May 20, 2022

New Receipt Validation Sample Code

Apple (via Thomas Zoechling):

The following example code illustrates how to retrieve an identifier in macOS for validating an App Store receipt. Use the data returned to by the copy_mac_address function to validate the App Store receipt.

In the following Swift code, the io_service function uses IOKit to retrieve network interfaces as an optional IOKit object. The copy_mac_address function looks up an appropriate network interface and returns the hardware address from the IOKit object as optional CFData.

Apple’s sample code for this had been broken at least since Catalina. I still think there should just be an API for validating and parsing receipts, instead of encouraging developers to either reinvent the wheel or incorporate open source code of questionable quality.


Update (2022-06-08): macOS 13 adds AppTransaction.

Update (2022-06-10): John Siracusa:

Still no simple Apple API to validate Mac App Store receipts, though, right?

Rosyna Keller:

No. And I’m not sire there will be on as it’s a semi-antithesis of DRM checking: have your check in multiple places that can’t be stubbed out by one change to the binary especially if it would apply to every binary.

John Siracusa:

I just don’t want to have to statically link OpenSSL into my dinky app and then copy/paste a bunch of code I don’t understand. I’m not trying to stop someone with a hex editor. I’m trying to stop casual piracy.

Rosyna Keller:

This new API is for making feature selections betterer based on date purchased without needing to roll a new app entry on the App Store for the new version.

3 Comments RSS · Twitter

Broken how..? I‘m only aware of a rare issue where the wrong network interface is being checked causing the validation to fail.

@Jan Yes, broken in that it uses the wrong MAC address.

Apple sample code is different to internal implementation in AppleMediaServices.framework (+[AMSDevice macAddressData]).

Sample Code

if wantBuiltIn == CFBooleanGetValue(isBuiltIn) {
return candidate
Internal Code

if CFBooleanGetValue(isBuiltIn) == true {
return candidate
} else if wantBuiltIn == false {
return candidate
} else { //interface is not build in but we want build in

Another difference is:

guard let service = io_service(named: "en0", wantBuiltIn: true)
?? io_service(named: "en1", wantBuiltIn: false) //instead of true
?? io_service(named: "en0", wantBuiltIn: false)
Due to the difference I would even rename wantBuiltIn to shouldFailIfNotBuiltIn

Leave a Comment