MTLDoc

Code Signing Hell with XCFramework

XCFramework Code Signing

If you’ve ever dealt with code signing issues in iOS or macOS apps, you know how challenging it can be. Code signing is typically something that just works, but when you encounter errors, it can be difficult to determine what went wrong. Seems that code signing problems are so unique that it can be hard to find solutions in Google, and you have to figure out the problem on your own.

This post is about one such unique issue that I encountered. I don’t know if anyone else has faced the same problem, as I couldn’t find any information about it. The issue involved signing an internal XCFramework with a C++ library that was distributed as a Swift Package, added as a dependency to another package, and only appeared on macOS.

App Store Validation Error

Asset validation failed

Invalid Code Signature Identifier. The identifier “SomeFramework-8885494daf37cec1267d771a9cdwi75f0196184d1” in your code signature for “SomeFramework” must match its Bundle Identifier “SomeFramework” (ID: 909f33e1-f60b-4ed2-8f0b-574b0cd93e6f)

This error first arose when I tried to build a macOS app with a dependency that worked fine in iOS apps. This was a regular app without any macOS-specific capabilities such as hardened runtime, which might have stricter code signing rules. The app had the same build pipeline as our iOS apps, so the problem was specific to macOS and like any code signing issues only occurred in the release configuration.

XC++Framework

The dependency in question was a Swift package that contained another dependency, a prebuilt XCFramework from a C++ library with binaries for different Apple platforms and architectures, also wrapped in a Swift package. I won’t go into detail about the process of building a C++ XCFramework and distributing it as a Swift package, but I will mention that we used the swift-create-xcframework tool for building.

If you’re not familiar with XCFrameworks, they are essentially folders with an Info.plist file and separate folders for different platforms and architectures, such as iOS, macOS, watchOS, Mac Catalyst, Simulator etc.

XCFramework Structure

For the purposes of this issue, we will focus on the iOS and macOS versions, as they had the most noticeable differences. One important thing to note is that the macOS version of the framework has a _CodeSignature folder, while the iOS version does not. This is why the problem only appeared on macOS.

Codesign for the Resque

There is a tool called codesign that is used to sign your app during the build process, and it also allows you to inspect the current signature of apps and frameworks. To see the signature of a framework, you can use the following command:

codesign -dv SomeFramework.framework

The -dv flag stands for the --display and --verbose options. You can find more information about the codesign command and its options here or by running man codesign in Terminal.

If you run this command for an iOS framework, the output will be:

SomeFramework.framework: code object is not signed at all

But for a macOS framework, you’ll see:

Executable=/path/to/executable
Identifier=SomeFramework-8885494daf37cec12e67d771a9cdwi75f0196184d1
Format=bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20400 size=28550 flags=0x2(adhoc) hashes=886+3 location=embedded
Signature=adhoc
Info.plist entries=20
TeamIdentifier=not set
Sealed Resources version=2 rules=13 files=3
Internal requirements count=0 size=12

Notice that the Identifier here is the same as the one in the error message from the App Store above, and it definitely doesn’t match the framework’s bundle identifier, which is simply SomeFramework. This is where the source of the error becomes clear. Also, note that the TeamIdentifier is not set here, and the framework needs to be resigned during the app build. But why is it resigned with the same identifier?

To find out, I started inspecting the Xcode build logs for the app and found that Xcode was running the following command to sign the framework:

/usr/bin/codesign --force --sign - --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der /path/to/framework/embedded/inside/app

And here is the issue! When signing the embedded framework, Xcode updates the signature but preserves the identifier (using the --preserve-metadata option). Since this XCFramework is inside a regular Swift package, I can’t embed it without signing. This option is only available for frameworks, but not Swift packages. So I need to figure out how to sign it during XCFramework creation, but with a valid identifier.

The codesign tool allows you to set the signature identifier using the -i option, and the swift-create-xcframework tool has an --xcconfig option to pass a .xcconfig file with a list of Xcode build settings that will be used during XCFramework creation.

One relevant build setting is OTHER_CODE_SIGN_FLAGS, which is a list of additional flags for codesign.

OTHER_CODE_SIGN_FLAGS=-iSomeFramework

Finally, after three days of investigation, I rebuilt the XCFramework with the proper identifier using this one line of code. When Xcode signed the macOS app, it preserved this identifier, and the App Store validation process passed successfully.

I also tried removing code signing at all by using the CODE_SIGNING_REQUIRED=NO flag, which would prevent the macOS framework from being signed. However, this didn’t help, as Xcode still generated the same identifier with an additional hash when signing the app. The only solution was to sign the framework with the proper identifier.

Conclusion

I don’t know if this post will help anyone fix code signing errors, as they are always unique. It’s possible that the problem was caused by the C++ nature of the framework, and that a Swift XCFramework would have had the proper identifier from the start. However, there is very little information available about these kinds of developer problems on the internet, so I decided to write about it in the hope that it will help someone avoid spending three days troubleshooting and instead focus on more productive tasks.

If you have any questions, feel free to ask them via email at contact@mtldoc.com or connect with me on Twitter at @petertretyakov. Keep in mind that I am not a code signing expert and really appreciate it when these security things just work :) I hope your day is free of build errors!