Verifiably signed app becomes unsigned once downloaded from Steam

Hello! I'm dealing with a strange code signing issue which is preventing me from distributing a game through Steam. I'm able to sign and notarise the app in Xcode without any issues. I can verify that the app and all frameworks in /Contents/Frameworks/ are signed, and Gatekeeper allows the app to run without complaining.

$ spctl --assess -vvv ~/Temp/CodeSigningTest/GoodApp.app
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: Ruairi Dorrity (3F97UA4BF8)

$ codesign --verify -vvv ~/Temp/CodeSigningTest/GoodApp.app
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/ogg.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/ogg.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/mpg123.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/mpg123.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/libmodplug.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/libmodplug.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/freetype.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/freetype.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/Lua.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/Lua.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/vorbis.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/vorbis.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/OpenAL-Soft.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/OpenAL-Soft.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/theora.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/theora.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/love.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/love.framework/Versions/Current/.
--prepared:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/SDL2.framework/Versions/Current/.
--validated:/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/SDL2.framework/Versions/Current/.
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app: valid on disk
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app: satisfies its Designated Requirement

However, if I zip the app and upload it to Steam, the app that the Steam client downloads is blocked by Gatekeeper ("damaged and can't be opened") and re-running the above commands shows that the code signing seal has been broken somehow on the downloaded app:

$ spctl --assess -vvv ~/Temp/CodeSigningTest/BadApp.app
/Users/ruairi/Temp/CodeSigningTest/BadApp.app: cannot find code object on disk

$ codesign --verify -vvv ~/Temp/CodeSigningTest/BadApp.app
/Users/ruairi/Temp/CodeSigningTest/BadApp.app: code object is not signed at all
In subcomponent: /Users/ruairi/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/love.framework

The second command can be re-run, showing a seemingly random framework from /Contents/Frameworks/ each time e.g.

$ codesign --verify -vvv ~/Temp/CodeSigningTest/BadApp.app
/Users/ruairi/Temp/CodeSigningTest/BadApp.app: code object is not signed at all
In subcomponent: /Users/ruairi/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/ogg.framework

Further investigation shows that these frameworks are now unsigned, when they were signed before uploading and downloading:

$ codesign --verify -vvv ~/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/ogg.framework
/Users/ruairi/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/ogg.framework: code object is not signed at all

$ codesign --verify -vvv ~/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/love.framework
/Users/ruairi/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/love.framework: code object is not signed at all

...

$ codesign --verify -vvv ~/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/ogg.framework
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/ogg.framework: valid on disk
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/ogg.framework: satisfies its Designated Requirement

$ codesign --verify -vvv ~/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/love.framework
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/love.framework: valid on disk
/Users/ruairi/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/love.framework: satisfies its Designated Requirement

I'm stumped as to what's happening here. Is is possible that the app is being modified being the scenes by Steam, which breaks the code signing? This seems unfathomable because it would surely break code signing on every Mac game on Steam, but I really can't understand what else would be going on. I'm sure I need to expand my knowledge on code signing; any pointers, suggestions or assistance is greatly appreciated! Thank you!

Problems like this are almost always the result of packaging problems, that is, not following the rules in Placing Content in a Bundle. It’s hard to say exactly what’s going on based on the info you posted. Your app’s structure looks better than most (-:

One thing to check is that the symlinks in the .framework bundles survived the zip and unzip process. I regularly see folks run into weird problems when tooling replaces these symlinks by copies.

Share and Enjoy

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

Hi Quinn! Always a pleasure to hear from you.

One thing to check is that the symlinks in the .framework bundles survived the zip and unzip process. I regularly see folks run into weird problems when tooling replaces these symlinks by copies.

Wow, this was a great shout, it does look like the symlinks are being replaced by copies! e.g.

$ ls -la ~/Temp/CodeSigningTest/GoodApp.app/Contents/Frameworks/Lua.framework
total 0
drwxr-xr-x   6 ruairi  staff  192 25 Feb 16:44 .
drwxr-xr-x  12 ruairi  staff  384 25 Feb 16:44 ..
lrwxr-xr-x   1 ruairi  staff   24 25 Feb 16:45 Headers -> Versions/Current/Headers
lrwxr-xr-x   1 ruairi  staff   20 25 Feb 16:45 Lua -> Versions/Current/Lua
lrwxr-xr-x   1 ruairi  staff   26 25 Feb 16:45 Resources -> Versions/Current/Resources
drwxr-xr-x   4 ruairi  staff  128 25 Feb 16:44 Versions
$ ls -la ~/Temp/CodeSigningTest/BadApp.app/Contents/Frameworks/Lua.framework
total 24
drwxr-xr-x   6 ruairi  staff  192 25 Feb 16:53 .
drwxr-xr-x  12 ruairi  staff  384 25 Feb 16:52 ..
-rwxr-xr-x   1 ruairi  staff   24 25 Feb 16:53 Headers
-rwxr-xr-x   1 ruairi  staff   20 25 Feb 16:52 Lua
-rwxr-xr-x   1 ruairi  staff   26 25 Feb 16:53 Resources
drwxr-xr-x   4 ruairi  staff  128 25 Feb 16:53 Versions

If I 'fix' BadApp.app by copying the original symlinks from GoodApp.app, it passes all the code signing checks, so I feel pretty confident this is the issue now. I also managed to find 4 year old Steam and Reddit threads from the same user who had the same issue with symlinks being replaced in their app; their solution was using Steam's command line interface to upload instead.

https://www.reddit.com/r/gamedev/comments/egsw7v/steamworks_trouble_with_symlinks_when_uploading/

https://steamcommunity.com/discussions/forum/2/1747892438661514782/

I'll resort to this if it's the only way to make the app work, but just to exhaust other options first: is it possible to modify my app's build process in Xcode so the symlinks are replaced with copies before signing and notarisation? Are there good reasons not to do that?

Thank you!

Verifiably signed app becomes unsigned once downloaded from Steam
 
 
Q