MacAdmins Podcast

How Signing and Privacy Xcode Options For Developers Translate to MacAdmins

We had Greg Neagle on the MacAdmins Podcast last night and I found myself struggling with a few words because in some cases what we see in Xcode doesn’t match the terminology we use in device management.

Insert a little stammering around to try and phrase things properly…

A few minor details about the structure of an Xcode project. The name of the project is the top level of the hierarchy on the left. A lot of what matters in device management is how a project is signed and the making sure we can provide the ability to run a project and the capabilities the project needs with the least amount of friction. Apple has a number of APIs that enable us to bypass various click throughs, or prompts when a user opens an app we compile. The little play button compiles the project. In the hierarchy navigation, we can re-order objects but by and large we have a folder with our swift code, followed by one for each extension we load into our code, followed by one for packages, then one or more for unit tests, one for Products, and then one for Frameworks. At the same level of the hierarch as the name of the project, we’ll then see Package Dependencies, which are any third party packages brought into the produce (e.g. Auth0 for federated identities, XCTestToolkit to help with unit tests, or any we actually create for distribution that might work between our Mac and iOS apps (yes, most of us still have a project for each of those).

Most who do device management won’t care much about the code itself. An app does a thing. We might care about extensions as those have become more important again (again as they were important in the MacOS 6 through MacOS 9 days). Each extension loads in a collection of APIs from Apple. They take up less space in memory than, let’s say, a framework or swift package we write, as the underlying components that make up an extension are part of the operating system. They can be a challenge to work with as we have to be intentional about how we exchange data between them. For example, we might need to create an app group so information can flow across core data between extensions. They can also have their own target and so work in a different sandbox.

Note: Package in swift is a collection of some swift code we can import, not an installer package.

The part that most of us who do device management care most about is that sandbox and the signing required. To see that, we can click on the name of a project and then “Signing & Capabilities” in the sub-navigation bar and in the list of Targets select one (click through each to get familiar).

The Signing & Capabilities portion of our app configured property lists, based on the developer. There are a few sections here. Most of us allow Apple to automatically manage the information. We create a Team, which could be an individual developer’s Apple Developer account or a team account for when there are multiple developers. Anyone who downloads a project from a public GitHub repository will have seen this section in red. That’s because it was signed by the Apple ID of whoever wrote it and needs to be moved to an account that has the appropriate certificates in the keychain. There should be a button to sign something appropriately if it’s red. Provided the automatic option is configured that should take care of things. This is also where we create a bundle identifier. That needs to be unique for each target. This gets placed in a property list for each version of the app and can increment for each. So for example, if I made an app called “Dungeon Craul” I might put com.krypted.dungeoncraul in for my first version but then as I increment I might do com.krypted.dungeoncraul1. If I have a watch extension so there’s a watch view I might call that com.krypted.dungeoncraul.watchextension or there’s a Message Filter extension I might call it com.krypted.dungeoncrawl.messagefilter. Wait, there’s a typo in the previous one. That is one of the many reasons an app can fail to compile. Sometimes we have to hunt around in the Build Settings tab to figure out what we did wrong or Google any errors that produce a red x in the navigator when we attempt to compile. For more on how to get entitlements for some of these, see https://krypted.com/swift/developer-mode-system-extensions-on-macos/

In the above screen, there’s also App Groups. These are able to exchange resources between apps or between extensions within apps. Below is the App Sandbox section. These are those pesky entitlements the app is going to ask users for. Some apps track those in a .entitlements file:

cat "/Applications/Resize Lite.app/Contents/Resources/app_signed.entitlements" com.apple.security.app-sandbox com.apple.security.inherit %

As Greg put it, it’s great to provide transparency to the user, but they can also create a form of click fatigue. We can always undo these with the tccutil command. Each checkbox we use can, on first use, prompt the user to approve the access. If the app loads code that accesses these capabilities on first open, then we might prompt a user several times.

The next section likely doesn’t get noticed by most systems administrators but it’s good to know about, and that’s the hardened runtime. At this point, most developers should have fixed any issues that cause an app not to be able to run as a hardened runtime. There are also options in here that are necessary for legacy code to work (a lot of which starts with NS*). For example, if a developer has dynamic libraries they still load, they may enable DYLD Environment Variables. I often enable Debugging Tool while testing but otherwise try to disable all of these when possible. I want nothing to invade the memory space of an app unless it has to. Especially if the app loads Network, Message Filter, Credential Provider, Persistent Token, Mail, Safari, or Smart Card Token services. While it’s still bad for, let’s say a Custom Keyboard extension, it’s not as dangerous. For a list of each Extension and the APIs that come with each, see https://developer.apple.com/app-extensions/.

Most of the rest of an Xcode project is the logic of the app and administrators often don’t need to know that much, unless they’re actually updating that logic. Projects that call external dependencies can’t build and run until those are downloaded, and they can run older versions, so when doing an analysis of the security of a given app it’s good to make sure the version or dependency doesn’t have any flaws on an ongoing basis. I like to keep an external artifactory instance of all external dependencies in case one disappears on me (it’s happened) and to make sure the version I use in each release is available in the artifactory repo. That’s just old dependency management tactics but still sound.

Let’s look at what is available about a compiled app. We’ll look at Slack to make this easy. We can run the codesign command with the -dv options to see the ID and TeamIdentifier:

codesign -dv /Applications/Slack.app

Then we can find the Identifier and TeamIdentifier in the output:

Executable=/Applications/Slack.app/Contents/MacOS/Slack Identifier=com.tinyspeck.slackmacgap Format=app bundle with Mach-O universal (x86_64 arm64) CodeDirectory v=20400 size=477 flags=0x0(none) hashes=4+7 location=embedded Signature size=4698 Info.plist entries=36 TeamIdentifier=BQR82RBBHL Sealed Resources version=2 rules=13 files=269 Internal requirements count=1 size=228

We might additionally need the code requires, which can be found with the -dr option (and an extraneous hyphen for good measure):

codesign -dr - /Applications/Slack.app

These are primarily certificates, team identifiers, and the bundle identifier. In Xcode we just call that the checkbox to let apple manage things (for the certs), the Team, and the Bundle Identifier. We can then see the info.plist referenced in the earlier codesign output. To do so, control-click on the app itself and click on Show Package Contents.

There’s a contents directory in each app bundle and within that there should be a number of different keys (for more on what all is in there, see https://krypted.com/uncategorized/whats-in-an-app/), which include the Bundle Identifier and a TeamID (although if it’s a write-once, compile many app it might have a team ID with the write-once, run many app). This should mostly match up with what we see in the Info tab back in Xcode.

Those keys can then populate certain aspects of the app, like a copyright notice, icon, name of an app, etc. The Bundle Version key usually needs to change with new releases of an app onto the App Store, although sometimes (forks, major releases) versioning information can go into the bundle ID, which then has to be added to PPPC controls within an MDM or profile. The info.plist can also have information about specific privacy controls so they can include a custom message. For example, rather than just indicating an app needs to access Bluetooth, the app can explain why (and potentially what features won’t work if the entitlement is not given). Any changes to these folders will cause the signature to break and an app will no longer to able to run, so consider this read-only information.

The signed and notarized app will tell people not to break it, but if there’s a strong desire to do so, it is possible. I wrote an app that was specifically meant to resign .ipa files. Those are similar-ish to those .app bundles) distributed by the App Store where the iOS App Store Package (or .ipa for short) is an application archive file that’s just a simple zip with a binary that’s run on iOS (or now M series-based Macs). Those are easy enough to compress and uncompress as they’re just zips. Codesign itself is open sourced by Apple https://opensource.apple.com/source/security_systemkeychain/security_systemkeychain-55191/src/ and there are tools like zsign and an older project called isign that can somewhat do signing for iOS and it’s not terribly different on the Mac.

The reason that comes up is how the community will have to deal with open source applications that aren’t signed or distributed but are easy to distribute (e.g. Munki Self Service). There are lots of new options in Xcode Cloud ( https://developer.apple.com/documentation/xcode/writing-custom-build-scripts ) to automate build features or xcodebuild still works (as does xcrun with the simctl verb).

One thing that came up on the podcast with Greg was inheritance, or who owns with process that’s running and what permissions do those processes have. Greg referenced making a C wrapper to invoke or spawn a process. The reason for C is that if we launch an app from let’s say Terminal, the app will inherit the App Sandbox capabilities section of Terminal. In swift we can instantiated a process in our app using, let’s say:

[task setArguments:@[@"-a", @"/Applications/Myradapp.app"]];

When this came up with Greg, I wasn’t sure what the Apple documentation said so dug that up: https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW15. Now it’s worth noting that this is different than permissions. Root is one thing, sandbox profiles (those pesky .sb files that come up) can restrict root.

$ sandbox-exec -f config.sb /Applications/krypted.app/Contents/MacOS/krypted

We can run the sandbox-exec command to sandbox an app with a specific profile, although that should be done with the Xcode project instead. There used to be some ruby scripts like container_check.rb and asctl to control some of the sandbox functionality but as we’ve moved to a more and more secure OS those have often fallen away. Another way to do some of this might be a shared service using a bamboo or other build automation tool ( https://krypted.com/apps/adding-app-notarization-for-macs-to-your-build-train/ ). At this point, hopefully the way a developer sees entitlements and how a developer configures sandboxes (once manual a la sandbox-exec, now fairly automatic) and how what we put where translates to admins seems a little more clear.

Anyway, looking forward to seeing what Greg comes up with and thanks as always for joining us for an episode if you happen to see this (and make it this far after what I’m sure will be some massive eye rolls)!