Cannot activate Network Extension signed with a Developer ID certificate

I have a simple CLI app bundle that activates my system extension. When I sign it for development it works fine. However, once I sign it with my developer ID certificate for distribution, the network extension will not activate, getting stuck the activation request and completely killing any internet connectivity until I restart.

The only thing that I see is different is when I call systemextensionsctl list I get something like:

1 extension(s)
--- com.apple.system_extension.network_extension
enabled	active	teamID	bundleID (version)	name	[state]
		<TEAM_ID>	com.company.networkExt (1.0/240116145656)	-	[validating by category]
*	*	<TEAM_ID>	com.company.networkExt (1.0/240115061310)	ProxyExtension	[activated enabled]

Where the one specifying [validating by category] is the one that I'm trying to activate signed with the developer ID cert. The one that is [activated enabled] got there from a dev build.

The app was built and notarized and shows to be valid by any codesign -dv --verify --strict and spctl commands that I've found. The system extension is also valid according to codesign.

The entitlements are adjusted to use the -systemextension suffix to work with Developer ID certificates.

Is there another step required to make it work with a developer ID certificate?

Replies

I have a simple CLI app bundle that activates my system extension.

This isn’t something we support. See this post. I recommend that you repeat these tests with code in the container app running that app in the GUI.

The entitlements are adjusted to use the -systemextension suffix to work with Developer ID certificates.

Did you also update the provisioning profile? I talk about this in same detail in Exporting a Developer ID Network Extension.

Share and Enjoy

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

  • This is surprising to me as the CLI works perfectly fine when signed with a development certificate and provisioning profile, both when automatically signed by xcode and manually signed with a development cert.

    So what is not supported? CLI system extension activation in general or just Developer ID signing for it?

  • +1 for repeating the tests with code in the container app running that app in the GUI.

Add a Comment

Thanks for your reply @eskimo . I did update the provisioning profile.

I think I'm missing the bigger picture here. In my older post I was trying to make this run from a daemon, and you recommended that I bundle that daemon as a separate app. So I followed Signing a daemon with a restricted entitlement, and it complicated things a lot, but I made it work. I encountered this same issue back then, but decided to simplify the problem and re-write the interaction with the Network Extension as a CLI that could be started from that daemon more easily without any entitlements in the daemon. It's a simple CLI, only activates/deactivates starts/stops the network extension. During development it was fine, only when signing for distribution it would fail.

If I understand correctly, the problem is that the CLI doesn't have a GUI? Could I then have a very short lived window (or perhaps just something in the dock) that applies the same logic as the CLI and exits? That'd be strange, but besides it being annoying to users, why would that not work?

Thanks!

If I understand correctly, the problem is that the CLI doesn't have a GUI?

No. The issue with the System Extensions framework is that it was designed to be called by GUI apps in response to user interactions. It was never intended to be used from a command-line environment, from installer packages, and so on. That doesn’t stop folks from trying to use it that way, but my experience is that those techniques are brittle and thus DTS doesn’t support them.

If you plan to deploy your product in a managed environment, use MDM to do this stuff. If you plan to deploy your product to normal users, I recommend that you use System Extensions framework in the way that it was intended to be used.

Share and Enjoy

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

In case anyone has a similar issue, here's a script condensing my learnings from @eskimo's posts.

Take it with a pinch of salt!

# Assumes all variables are populated already, with app and extension built using a development certificate.

# Extract the entitlements from the builds. They differ from the .entitlement files in the project!
codesign -d --entitlements :- "${app_bundle}" > app.entitlements
codesign -d --entitlements :- "${extension_bundle}" > sext.entitlements

# We only use one, specify any others as needed
sed -i '' 's/'$networkEntitlement'/'$networkEntitlement'-systemextension/g' app.entitlements
sed -i '' 's/'$networkEntitlement'/'$networkEntitlement'-systemextension/g' sext.entitlements

# Copy the system extension into the app bundle
mkdir -p ${app_bundle}/Contents/Library/SystemExtensions/
ditto ${extension_bundle} ${app_bundle}/Contents/Library/SystemExtensions/${extension_bundle}

# Replace the embedded provision profiles
ditto ${app_provision_profile_path} "${app_bundle}/Contents/embedded.provisionprofile"
ditto ${extension_provision_profile_path} "${app_bundle}/Contents/Library/SystemExtensions/${extension_bundle}/Contents/embedded.provisionprofile"

# Sign the System extension bundle first, then the binary with entitlements. Both require hardened runtime
codesign -f --timestamp --options runtime --sign "${certificate_name}" ${app_bundle}/Contents/Library/SystemExtensions/${extension_bundle}
codesign -f --timestamp --options runtime --entitlements "sext.entitlements" --sign "${certificate_name}" "${app_bundle}/Contents/Library/SystemExtensions/${extension_bundle}/Contents/MacOS/${extension_id}"

# Sign the App bundle first, then the binary with entitlements. Both require hardened runtime
codesign -f --timestamp --options runtime --sign "${certificate_name}" ${app_bundle}
codesign -f --timestamp --options runtime --entitlements "app.entitlements" --sign "${certificate_name}" "${app_bundle}/Contents/MacOS/${app_name}"

# We cannot send the bundle directly to notarytool (it is a dir), zip it first.
ditto -ck --rsrc --keepParent --sequesterRsrc "${app_bundle}" app.zip
xcrun notarytool submit app.zip --wait --apple-id=${NOTARIZATION_EMAIL} --password="${NOTARIZATION_PASSWORD}" --team-id="$TEAM_ID"
# Staple assuming notarization was Accepted. Even if it fails, notarytool returns 0, so stapler will fail then.
xcrun stapler staple "${app_bundle}"

My problem was in the runtime flag not being included in the bundle, only the binary. That's (as far as I could tell) what would break my system's connection.

Thanks again for your help @eskimo