Code signing for local, dev/staging, and production

We have a MacOS application that we plan on distributing standalone (it'll be installed through MDM or directly, not through the app store). We utilize endpoint security and full disk access for this (enterprise) app. I have a makefile that uses codesign to sign the app inside-out. All that appears to work (i.e., when I try to run the app directly it functions as I expect it to).

What's the recommended way to allow the developers in my team to also sign the app for local development so it functions as close as possible to production? My first thought is to distribute the developer identity to their machines using MDM. However, ideally i'd like to rule out the ability for a developer who has the MDM profile assigned to export the keys. That really only leaves a centralized solution in place or disabling SIP on their system (which I don't want to do). Alternatively, would creating a separate identity for production make more sense, so that in the case the developer certificate is revoked, the production releases continue to function as normal (however, I assume this would also require creating two different profiles for the endpoint security entitlement--one for each certificate).

Thanks!

Derek

Accepted Answer

A product you sign for direct distribution must necessarily be signed with your Developer ID signing identity. Without that, you won’t be able to notarise it.

Developer ID signing identities are precious, and I have a whole bunch of thoughts on that in The Care and Feeding of Developer ID.

allow the developers in my team to also sign the app for local development … ?

Don’t give them all a copy of your Developer ID signing identity.

A better option is to:

  1. Gather the provisioning UDIDs of all your development Macs and add them to your team on the Developer website. See Developer Account Help > Register devices.

  2. Have each of your developers create an Apple Account [1].

  3. And add them all to your team with an appropriate role. See Developer > Support > Articles > Program Roles and Developer Account Help > Manage your team.

  4. They can then create their own signing identities, using Xcode or the Developer website. For the first, see Xcode > Settings > Accounts. For the second, see Developer Account Help > Create certificates.

  5. For the provisioning profile you have a couple of options:

    • You could create a single development profile that authorises all these devices and codes-signing identities. That has the advantage that you only need to this once.

    • You could have each member of your team do this. That each team member more flexibility, and means that you don’t have to regenerate and redistribute an updated development profile every time someone joins the team, renews their certificate, adds a device, and so on.

  6. And use those to sign your product for use on any of your development Macs.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] You might have already created them an Apple Account as part of the MDM setup. I’m not entirely sure whether you can add those Apple Accounts to your team. I have a lot more experience with standard Apple Accounts, rather than managed ones.

Thank you. I did a little more digging after writing this post yesterday, and better understand the difference now between certificate categories (distribution/development).

So the complexity with the developer build process is it seems like xcode manages all of this and is the "easy way" to do things. However, our project is primarily Go with some embedded objective-c. In other projects within our org, we have sort of a standard way of setting things up (using makefiles). For example, to get a development environment up for a specific project, we just clone and run "make dev" for consistency and sanity. I'm not ultra familiar with xcode, so I'm not sure if it's worth the hassle to have it run the go build, and i'm unsure of whether we can use CI if we do.

Somewhat related follow-up question: Is there a way to avoid touching the private key for the precious developer certs (i.e., have a hardware security module / HSM generate and store the key and use an audited service? We use code signing certificates internally in our app to validate configuration payloads came from us. We do this by using AWS KMS to gen/store the private key for our identity, then utilize their Sign/Verify APIs for code signing and verification (note i'm using code signing a little loosely here--what we do is not akin to what the codesign tool does). Because AWS KMS generates the key, we never have a developer touch it and it can never be extracted + we gain auditing on any uses of it.

Mostly weighing options on how to make this as painless for our internal developers as possible.

Thanks again!

So, Xcode is not magic [1]. Most of what it does you can replicate yourself using command-line tools, REST APIs, and so on.

Xcode is an excellent source of knowledge about how to build and sign Mac products. You don’t need to use Xcode to build your final product, but you can create a test project that kinda looks like your final product and then see how Xcode builds it. That doesn’t necessarily require you to build your Go code in Xcode. From the perspective of packaging and code signing, all compiled languages are roughly equal.

Is there a way to avoid touching the private key for the precious developer certs … ?

You can sign code with a key stored on a hardware token. See Signing code with a hardware-based code-signing identity.

That’ll help, but it’s not the ultimate solution IMO. There are still a number of drawbacks with using Developer ID for day-to-day development:

  • The system expects Developer ID builds to be notarised [2], and you don’t want to add that complexity to your at-desk builds.

  • It’s not uncommon for Apple to grant additional capabilities for development initial and then require you to re-apply for distribution. For example, I regularly see us do this for the Endpoint Security additional capability. If you use Developer ID for at-desk builds then you’ll be stuck if you end up needing one of these additional capabilities.

  • It’s much easier for individual developers to manage their signing assets for development signing. For Developer ID you would have to build centralised infrastructure for that.

  • If a Developer ID build escapes into the real world, its indistinguishable from one of your shipping products.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Mostly. There are a few things that it does that are hard to replicate from the outside.

[2] You most won’t notice this, because your at-desk builds are not quarantined. However, there are a few cases where it matters, for example, with kernel extensions and system extensions.

Thanks again. I ended up just following your advice from the original post and heavily documenting how to create developer (NOT distribution) certificates for my developers to use. Our distribution certificate is now just hooked into CI (and only there). Seems to work for our use cases!

There are still a few fun things to determine, like computing a developer or distribution 'code requirement' so we can enable the full disk access TCC config for our app (codesign -dr - foo.app wants to pin the CR to the developer's CN). However I think i'll just post separately about that at some point.

Thanks again! D

Code signing for local, dev/staging, and production
 
 
Q