Manual Code Signing Queries

I am working on an application with multiple helper apps and tools with a fairly nested structure, like the following
Code Block
A.app
└── Contents
  └── MacOS
    ├── A
    ├── B.app
    │  └── Contents
    │    └── MacOS
    │      ├── B
    │      └── D.app
    └── C.app

and have the following queries

I. How to sign bundles correctly?
Should the top-level bundle be signed or signing the internal contents is enough or both? For eg: Currently, after signing any nested code, I sign frameworks (and plugins) as codesign -s xxx ABC.framework/Versions/A/ABC, which is what I believe Xcode does. However, in this thread  the codesign command is run on the .framework directory.

2. How to verify that the app has been signed correctly?
I have encountered issues where the following codesign command reports no errors, but the app crashes on launch with Code Signature Invalid exception.
Code Block
codesign --verify --deep --strict --verbose=2|3|4 Foo.app


3. Do Mac App Store apps need to incorporate protections required for notarization?
As per docs, Mac App Store apps don't need to go through the notarization process, are they still required to enable hardened runtime, signature timestamps etc?

4. Can helper apps have symlinks that point outside their app bundle?
In the structure that I have shared, apps B, C and D share a lot of common frameworks. Can C.app's Frameworks directory be symlinked with D.app's Frameworks directly, even though it is pointing outside C.app's bundle?

Note: This app will be distributed via the App Store
Answered by DTS Engineer in 636829022

  1. How to sign bundles correctly?

You should sign each code item separately, from the inside out. For more specific advice, see my Signing a Mac Product For Distribution post.

2. How to verify that the app has been signed correctly?

Doing a codesign --verify against the top-level app is a good idea. You don’t need the --deep because, if your code is nested and signed correctly, verifying the top level will verify all the nested code.

I have encountered issues where the following codesign command
reports no errors, but the app crashes on launch with Code Signature Invalid exception.

Right. That’s typically because the code signature is valid but the code can’t be run due to sytem policy. Tracking down that problems can be quite challenging but there’s two common reasons:
  • The code signing identity is not allowed. For example, you can’t run code signed with a distribution identity.

  • The code signing identity requires that the code be notarised and it isn’t.

  • The code uses entitlements that must be allowlisted by a profile but aren’t.

3. Do Mac App Store apps need to incorporate protections required for
notarization?

No.

As per docs, Mac App Store apps don't need to go through the
notarization process, are they still required to enable hardened
runtime, signature timestamps etc?

No. However, it’s generally a good idea to enable the hardened runtime even on Mac App Store (MAS) apps. That way you’ll flush out any problems that could cause you grief if you decide to ship a Developer ID build in the future. Indeed, many MAS developers maintain a Developer ID for their beta testing.

4. Can helper apps have symlinks that point outside their app bundle?

Yes, as long as the symlinks don’t point outside of the outermost bundle. However, this is not the right way to solve your problem:

In the structure that I have shared, apps B, C and D share a lot of
common frameworks.

Rather, you should use an rpath.

Share and Enjoy

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

I am working on an application with multiple helper apps and tools with a fairly nested structure

Why? I'm not even sure this will work at all via the Mac App Store. The documentation says you can have other, standalone executables in the MacOS folder, but I don't know if other app bundles qualify.

Technical Note TN2206 also specifically says you can have "helper apps and tools" inside the "Contents/Helpers" directory. Again, I'm not sure about actual app bundles though. Even if it is allowed, it seems to be risky. Few people do this. So if there is a problem, you are the only one who will notice.

Could these apps be changed to single-file tools or XPC bundles?
  1. It is good that you are comparing to what Xcode does. Does that mean you aren't actually using Xcode? That's a Red Flag. But if you can hack up a reasonable facsimile of your app and observe how Xcode signs it, you should always copy what Xcode does. Ignore what you see on the internet about it unless it was written by eskimo. After checking, I see that you are actually referencing one of eskimo's how-to threads. So again, you're on the right track. In that thread, eskimo is specifically talking about signings a complex executable not using Xcode. You haven't posted all of Xcode's operations, but it looks like Xcode is doing the same thing by signing a deeply tested framework tool first.

  2. That's a more difficult question. I don't really know the answer. Normally, for the Mac App Store, you just sign your app and submit. Then Apple re-signs and publishes. If you aren't using Xcode, then you are opening yourself up to the possibility of a code-signing error on submission. Hopefully the stand-alone submission tool will catch that problem. If not, hopefully App Review will catch it.

  3. Mac App Store apps don't have to be notarized, or even use the hardened runtime. But if you want to turn all those things on anyway, it won't hurt. Plus, it seems logical that the hardened runtime will be required in the Mac App Store at some point. Perhaps it already is and I just haven't noticed it. My Mac App Store app has the hardened runtime enabled. I think I must have done that myself when I started notarizing the non-Mac App Store version, just to be on the safe side.

  4. I don't know the exact answer for this either. However, it should be easy enough to test. I can tell you that you shouldn't use symlinks. Even if it worked, don't do that. The example that you would want to follow here is LoginItems. LoginItems are full app bundles stored in "Contents/Library/LoginItems" of your app. While I don't use them in my own app, I have other apps that do. And I can see that those apps definitely share frameworks with the top-level app. The tricky part is the linking. When I do an "tool -L" on the login item, it reports that the frameworks are referenced directly from "@rpath" without any ".." parent links. So this LoginItem executable has the correct RPATH encoded in it. It is possible that using ".." might still work, but I strongly recommend using the correct RPATH. This might require some research. It is the kind of thing that Xcode does for you. If you are doing it on your, you will have to figure it out on your own. In terms of tricky linking, RPATH is where it's at.

You may need to use the "install_name_tool" tool to set those paths inside the executable. You might need the "-headerpad_max_install_names" linker flag too.

The documentation says you can have other, standalone executables in
the MacOS folder, but I don't know if other app bundles qualify.

Table 3 of Technote 2206 macOS Code Signing In Depth is pretty clear that Contents/MacOS/ can host both apps and tools. Lot’s of folks do this. It isn’t a problem.

Share and Enjoy

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

  1. How to sign bundles correctly?

You should sign each code item separately, from the inside out. For more specific advice, see my Signing a Mac Product For Distribution post.

2. How to verify that the app has been signed correctly?

Doing a codesign --verify against the top-level app is a good idea. You don’t need the --deep because, if your code is nested and signed correctly, verifying the top level will verify all the nested code.

I have encountered issues where the following codesign command
reports no errors, but the app crashes on launch with Code Signature Invalid exception.

Right. That’s typically because the code signature is valid but the code can’t be run due to sytem policy. Tracking down that problems can be quite challenging but there’s two common reasons:
  • The code signing identity is not allowed. For example, you can’t run code signed with a distribution identity.

  • The code signing identity requires that the code be notarised and it isn’t.

  • The code uses entitlements that must be allowlisted by a profile but aren’t.

3. Do Mac App Store apps need to incorporate protections required for
notarization?

No.

As per docs, Mac App Store apps don't need to go through the
notarization process, are they still required to enable hardened
runtime, signature timestamps etc?

No. However, it’s generally a good idea to enable the hardened runtime even on Mac App Store (MAS) apps. That way you’ll flush out any problems that could cause you grief if you decide to ship a Developer ID build in the future. Indeed, many MAS developers maintain a Developer ID for their beta testing.

4. Can helper apps have symlinks that point outside their app bundle?

Yes, as long as the symlinks don’t point outside of the outermost bundle. However, this is not the right way to solve your problem:

In the structure that I have shared, apps B, C and D share a lot of
common frameworks.

Rather, you should use an rpath.

Share and Enjoy

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