To activate paid features in the app, Secrets has always done local receipt validation.
Succinctly validation involves two steps:
- Checking the signature on the receipt is valid.
- Checking if that receipt was generated for the current device.
For the second step you need a device identifier which must match the one the App Store used to generate the receipt. And here lies the problem.
On iOS getting a device identifier is very simple, you just call identifierForVendor
on UIDevice
. On macOS, however, Apple provides this function on their documentation that you should copy to your project and get the identifier using that.
A single API call would be better, but at least they give you a function you can just copy/paste right? Well… the thing is… it doesn’t work.
If you look a the code you’ll see that, apparently, the device identifier on macOS is just the MAC address of your en0
interface. But that’s not actually true. What happens if the user doesn’t have an en0
interface? Sounds unlikely? It’s not:
PSA to all devs doing local receipt validation on a Mac App Store app: the Apple provided sample code to get the computer's GUID may not work all the time:https://t.co/vMXxq0s4RY
— Paulo Andrade (@pfandrade_) February 2, 2021
In reality the device identifier for macOS is the MAC for the primary network interface, which might not actually be named en0
. To be on the safe side, instead of trying to determine which interface is the primary, I just grab all the MACs on that Mac and attempt validation with each one. Problem solved! Right?
Well… not yet. There’s another issue I just encountered recently. The IOBSDNameMatching
call to get an interface by name can actually return nil even when ifconfig
shows an interface with that name. I haven’t been able to understand why this happens (it’s very rare), but on one occasion Vallum Firewall seemed to be the culprit and on another, a simple restart seemed to fix it.
I finally ended up with a solution based on Apple’s source code here. Instead of getting a list of interface names and then getting the MAC for each by name I just iterate all the interfaces irrespective of what they’re called and collect the MACs for each.
And that’s it. Or at least I hope so… 😥