Porting NetworkExtension packet tunnel VPN to SystemExtension

We're looking at taking a Network Extension VPN implemented as an App Extension, and porting it to a System Extension. We still intend to distribute through the app store as well, but have requests for out-of-store distribution.

I remember seeing a thread about this some time back, but I haven't been able to locate it. If someone has a link to that thread please point me that way :-)

We have some questions about bundle IDs, APIs, and entitlements, because we're looking to minimize customer disruption.

  • Can we just add the SystemExtension entitlement to the current App ID for the network extension, update the profile, and continue with the same ID, or will we need to define a new ID?

  • What will happen if someone installs the application from a package outside the app store, and then goes to the app store page for the application? Will the App Store recognize that the app is already installed? What about version differences?

  • It looks like our management app can still use sendProviderMessage to communicate with the extension, and that we don't need XPC unless we want to give other applications the ability to talk with the extension. Is this correct?

  • Can a System Extension use the NSWorkspace openURL API?

  • It looks like a System Extension doesn't have the option of writing to a file, because it's not running as a logged in user and doesn't have access to a user's file systems. Is this correct?

  • For certificate-based authentication the SystemExtension needs to be able to access and use a client certificate. Is there a recommended way for the extension to access the client certificate, e.g, if the extension starts without the GUI because of an on-demand VPN configuration? In that case it doesn't seem like it would have keychain access.

Can we just add the SystemExtension entitlement to the current App ID for the network extension, update the profile, and continue with the same ID, or will we need to define a new ID?

This question doesn’t make sense. An App ID has capabilities which flow into the entitlement allowlist in your provisioning profile. When you create a profile from an App ID with the Network Extension capability enabled, you get different results depending on the type of provisioning profile:

  • Most profiles end up with the standard NE values in the allow list.

  • Developer ID profiles end up with values that have the -systemextension suffix.

Last I checked, Xcode’s standard code signing infrastructure wasn’t able to handle this subtlety (r. 108838909), which means you need to manually re-sign your app for Developer ID distribution.

What will happen if someone installs the application from a package outside the app store, and then goes to the app store page for the application? Will the App Store recognize that the app is already installed? What about version differences?

It’s not uncommon for Developer ID and App Store apps to have the same bundle ID. In general, macOS treats two apps with the same bundle ID as the same app.

Historically this extended to the App Store upgrade process, where App Store would happily upgrade a Developer ID app. However, I haven’t seen that in a long time. Today I tried it — using Hex Fiend, which has both Developer ID and App Store versions — and App Store was pretty clear about not doing that upgrade, failing with the message:

“Hex Fiend” is Already Installed

To install this app, rename or move “Hex Fiend” from your Applications folder and try again.

[[OK]]

It looks like our management app can still use sendProviderMessage to communicate with the extension, and that we don't need XPC unless we want to give other applications the ability to talk with the extension. Is this correct?

Yes.

However, sysex packaging generally produces more complex interactions between the sysex and its container app, which means it often makes sense to switch to XPC.

Can a System Extension use the NSWorkspace openURL API?

No. Sysexes are effectively launchd daemons and it’s only safe for them to use daemon-safe APIs.

Also, this makes no sense conceptually. Your sysex is shared between all the users logged into the system. If it calls -openURL and multiple GUI users are logged in, in which GUI context would the app open?

It looks like a System Extension doesn't have the option of writing to a file, because it's not running as a logged in user and doesn't have access to a user's file systems. Is this correct?

It can write to files, just like any other sandboxed process. The difference is that it’s running as root, so it can’t, say, share an app group container with the app being run by a specific user.

Again, this makes no sense conceptually, because there’s an many-to-one relationship between users and your sysex.

For certificate-based authentication the SystemExtension needs to be able to access and use a client certificate. Is there a recommended way for the extension to access the client certificate, e.g, if the extension starts without the GUI because of an on-demand VPN configuration? In that case it doesn't seem like it would have keychain access.

A sysex, like other daemon code, can only use the System keychain. This is a file-based keychain. See TN3137 On Mac keychain APIs and implementations for more background on this.

If the client identity is installed by a configuration profile and referenced by VPN configuration in that same profile, it should land in the System keychain in a way that your sysex can use it. I don’t remember ever confirming that for myself, and it wouldn’t surprise me if you ran into problems with it.

If your want to support other approaches — for example, adding a UI to your container app that lets the user create a VPN configuration including a selected client identity — that’s possible. However, you won’t be able to install the identity from your app because your app can’t modify the System keychain. Rather, you’d use IPC to pass the PKCS#12 to the daemon for it to import it into the System keychain.

Share and Enjoy

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

Can we just add the SystemExtension entitlement to the current App ID for the network extension, update the profile, and continue with the same ID, or will we need to define a new ID?

This question doesn’t make sense. An App ID has capabilities which flow into the entitlement allowlist in your provisioning profile. When you create a profile from an App ID with the Network Extension capability enabled, you get different results depending on the type of provisioning profile:

Most profiles end up with the standard NE values in the allow list.

Developer ID profiles end up with values that have the -systemextension suffix.

The App ID configuration has an entry which is "System Extension". I shouldn't have used the term "Entitlement" since on the web site it talks about "Capabilities"...

In the case of an App Store distribution, which we'd like to keep doing, it wouldn't be a Developer ID profile, so this is really asking about that case and whether we can just add the System Extension capability to the ID & update the App Store distribution profile. I understand that we'd need a new profile for the out-of-app-store distribution.

The file and keychain answers are pretty much what we expected--we expect that we're likely to need to do some XPC to talk between the two modules.

The App ID configuration has an entry which is "System Extension".

Right. That’s a separate capability that’s common to all sysexes, not just NE. Your container app needs that to install (well, “activate”) your sysex.

In the case of an App Store distribution, which we'd like to keep doing, it wouldn't be a Developer ID profile, so this is really asking about that case

In that case the -systemextension suffix isn’t required.

whether we can just add the System Extension capability to the ID & update the App Store distribution profile.

You will need to do that, yes.

Share and Enjoy

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

Porting NetworkExtension packet tunnel VPN to SystemExtension
 
 
Q