At the beginning of the pandemic I wrote a tool called OnAir that changes the color of a Philips Hue light when you’re on some sort of audio or video call. It’s a helpful signal to your loved ones, roommates, or kids that you’re stuck in Videoconferencing Hell1.

It’s a neat tool, but I’m pretty sure I’m still the only user. That makes sense: some assembly is required, it requires Micro Snitch, and I don’t have a UI to go with it. But also: I didn’t have a package download for it; adventurous users had to download a Golang toolchain to build OnAir. Making someone download a build toolchain to use your app is mean in the year 2020 I’m told, so I wanted to fix that.

While reading about how the code signature and notarization processes work on macOS, somehow I came across Little Snitch’s Internet Access Policies and thought it’d be cool to include one. The tl;dr on IAPs are: you can compile your application with some added context about what calls it makes out to the Internet, and that context will show up when Little Snitch prompts you about a connection your application is making. It looks like this:

Example IAP

Little Snitch tells you that OnAir wants to connect to on port 443. It also mentions what OnAir does (“OnAir changes the color of IP-based lighting when you are on a audio or video call”), a link to the Github page, and a specific statement about connections to the domain. In the image above, it reads “OnAir uses the Hue Broker Server to discover Hue Bridge devices on your network. If denied, OnAir will not be able to find the Hue Bridge device on your network.”

Useful security information is useful. More applications should probably have useful information. Except, getting that useful information into a single binary when you’re not using Xcode is trickier than I anticipated. So how do? Enough of the recipe intro: here’s how to do the thing.

Building Your Personal Plist Pizza

An Info.plist file has attributes about the application. You’ll need to make one.

There are tools that can help with this like PlistEdit Pro. In this case “it’s just xml” so you’d be able to use what I threw together as a template for your project. PlistEdit Pro was nice to use, though.

If you’re wondering what some of the CoreFoundation (CF) keys and values are, Apple’s Core Foundation Keys page has the answer. Though, you probably only need the ones listed in the Code Signing Tasks doc.

We’re here to talk about IAPs, though: so take a look at the specification on Little Snitch’s Research Assistant developer documentation. To summarize, you’ll need to add a dictionary entry for each endpoint your application connects to. The text that shows up for the IAP is put into the Purpose and DenyConsequences fields.

You can also reference a string that is localized, which is a nice thing to do in general. The OnAir Info.plist does this - though only for English today2.

The One Weird Linker Trick

OK, so you’ve made your XML. How can you build it into your application?

Generally, an Info.plist lives as inside of an app bundle. However, it is also possible for the data to live as a text segment in a Mach-O binary. We’re going to do that with the power of some linker flags.

  1. Add something like -ldflags='-extldflags "-sectcreate __TEXT __info_plist $(shell pwd)/Info.plist"' to your go build command. I copied this from my Makefile, you may need to do something special to get the full path to the file.
  2. Toss an import "C" into your application’s imports.

The import "C" thing is important, even if you’re not doing Cgo things in your application. If you don’t import it, the ldflags component will be ignored, and you won’t see your IAP.

You can confirm that the text segment made it in to the binary by running otool -s __TEXT __info_plist ./path/to/your/binary. Convert it to human-readable text by piping the output to xxd -r.

I talk about this in the context of a Golang application - but this is a linker flag. I imagine you can pass the sectcreate flag to whatever your build toolchain uses and you’ll get that __info_plist section.

License to Distribute

Little Snitch will only display an IAP for a signed application, so get your credit card out. Apple’s Developer Program costs $99 a year, but: you get unlimited certificates! This is an unverified claim, but I have at least two (2) certificates now, which feels like a lot for me.

After your account is approved (it took ~10 min for me on a Saturday morning), open up Xcode, go to Preferences, pop into the Accounts tab, and add your Apple ID. Then, click Manage Certificates... and generate you a Developer ID Application certificate, and possibly a Developer ID Installer certificate. These will be saved directly into your keychain, though you should export them with a secure password and put them somewhere safe.

I should note that the certificate generation step is different if you have an enterprise account; I think you’ll need to send a Certificate Signing Request (CSR) to the person who manages your account. I gleaned this from googling, so at least something has been written about it if you find yourself lost at this point. You’ll probably need the --acr-provider option with altool later.

Since you probably have MFA turned on for your Apple ID, you’ll need an “Application Password” which you can make over on the (Apple ID Management Portal]( Kinda funny that the developer tools don’t support native MFA, but security is hard. Anyways, once you’ve got that one-time password, get it into your keychain under the id appleid by running:

xcrun altool --store-password-in-keychain-item appleid -u -p asdf-asdf-asdf-asdf

Signing Your Binary

Run security find-identity -v and get the hash for your Developer ID Application certificate. Sign your binary with codesign -s DEV_APP_HASH_HERE -f -v --timestamp --options runtime BINARY_PATH_HERE. Congratulations: you made a signed binary on macOS.

At this point, if you delete the Little Snitch rules that exist for your program and then run it, you’ll probably see the words that you wrote in the Little Snitch alert dialog box. Congrats!

Notarizing Your Binary

That’s fine and good, but let’s say you want to distribute your application. Apple’s notarization process seems to be a very quick static analysis pass on your binary to make sure that you’re not distributing obvious malware. So I tried to zip up my binary and ship it off to Apple… without much luck. It worked once, at least?

So instead I built a package. I moved my signed binary into an empty folder creatively named package/ and made an installer with this pkgbuild:

pkgbuild --root package --identifier com.example.yourapp.dontusethis.makeyourown --version $(VERSION:v%=%) --install-location /usr/local/bin onair-$(VERSION)-raw.pkg

Signed the package with my developer installer key (grab that hash using security find-identity -v):

productsign -s DEV_INSTALL_HASH_HERE --timestamp onair-$(VERSION)-raw.pkg onair-$(VERSION).pkg

And submitted for notarization 3

xcrun altool --notarize-app --primary-bundle-id com.example.yourapp.dontusethis.makeyourown --username '' --password "@keychain:appleid" --file onair-$(VERSION).pkg

Notarization takes a little bit of time: generally <5 min, but could be longer than an hour, too. Your request will get a UUID, which you can use to check on the status of notarization, or you can wait for the email. For the impatient, keep asking Apple for an update using this command:

xcrun altool --notarization-info UUID_HERE -u '' --password "@keychain:appleid"

If it passes, then staple your notarization to your package (xcrun stapler staple onair-$(VERSION).pkg) and distribute. I just uplodaded my package as part of a Github release.

Embedded Data? In A Binary?

Well, that’s about it. If you’re building a tool for users who might use Little Snitch, this is a nice way to give additional context on what your app is connecting to. Maybe there are other things you can put in this header that get parsed by the OS too! 😜

  1. Maybe they’ll save you, maybe they’ll let you suffer - but at least they’ll know what the situation is. ↩︎

  2. I’d absolutely accept localization PRs if you wanted to make one. ↩︎

  3. That appleid should say something else if you used a different value when you saved your credentials with altool earlier. ↩︎