pythonlinuxmacoscode-signingnotarize

How to staple Apple notarization tickets manually (e.g. under linux)


Recently (as of 2023-11-01) Apple has changed their notarization process.

I took the opportunity to drop Apple's own tools for this process (notarytool) and switch to a Python-based solution using their documented Web API for notarization

This works great and has the additional bonus, that I can now notarize macOS apps from linux (in the context of CI, I can provision linux runners much faster than macOS runners). hooray.

Since this went so smooth, I thought about moving more parts of my codesigning process to linux, and the obvious next step is find a solution for stapling the notarization tickets into application, replacing xcrun stapler staple MyApp.app

With the help of -vv and some scraps of online documentation, it turns out that it is very simple to obtain the notarization ticket if you know the code directory hash (CDhash) of your application.

the following will return a JSON-object containing (among other things) the base64-encoded notarization ticket, which just has to be decoded and copied into the .app bundle for stapling:

cdhash=8d817db79d5c07d0deb7daf4908405f6a37c34b4
curl -X POST -H "Content-Type: application/json" \
   --data "{ \"records\": { \"recordName\": \"2/2/${cdhash}\" }}" \
   https://api.apple-cloudkit.com/database/1/com.apple.gk.ticket-delivery/production/public/records/lookup \
| jq -r ".records[0] | .fields | .signedTicket | .value"

So, the only thing that is still missing for my stapler replacement is a way to obtain the code directory hash for a given application. On macOS (with the XCode tools installed), I can get this hash with codesign -d -vvv MyApp.app, but this obviously only works if I have the codesign binary at hand.

I've found a couple of python wrappers for stapling tickets, but all of them just call xcrun stapler staple under the hood. This is not what I want.

So my question is: How can I extract the code directory hash (CDhash) from a macOS application, without using macOS specific tools? (That is: How are CDhashes generated? I haven't found any documentation on this)

I would very much like to use use Python for this task. Ideally, such a solution would be cross-platform (so I can use it on macOS and Linux, and probably others as well).


Solution

  • How can I extract the code directory hash (CDhash) from a macOS application, without using macOS specific tools?

    The CDhash of an app is the CDhash of the main executable in Contents/MacOS as identified in Contents/Info.plist

    Each hash is stored at the end of the binary segment for each architecture in an XML statement. It can be grepped out.

    The embedded cdhash is encoded in base64. The first one is for intel, the second for apple silicon:

    % grep -i -a -A3 'cdhashes' myApp.app/Contents/MacOS/mainexec | sed -n '4p;9p' | cut -f 3  
    HPhKLQv1j2SFYTmIgyUi/L6B9Yo=
    TVNDrCQEL9A/DMWVmphntZAq7kc=
    
    % printf "HPhKLQv1j2SFYTmIgyUi/L6B9Yo=" | base64 -d | hexdump -v -e '/1 "%02x" ' && echo ""
    1cf84a2d0bf58f6485613988832522fcbe81f58a
    
    % printf "TVNDrCQEL9A/DMWVmphntZAq7kc=" | base64 -d | hexdump -v -e '/1 "%02x" ' && echo ""
    4d5343ac24042fd03f0cc5959a9867b5902aee47
    

    Compared with the cdhash as reported by codesign:

    % codesign -dvvv -a arm64 myApp.app
    Executable=myApp.app/Contents/MacOS/mainexec
    Identifier=com.mycompany.myApp
    Format=app bundle with Mach-O universal (x86_64 arm64)
    CodeDirectory v=20500 size=92199 flags=0x10000(runtime) hashes=2870+7 location=embedded
    Hash type=sha256 size=32
    CandidateCDHash sha256=4d5343ac24042fd03f0cc5959a9867b5902aee47
    CandidateCDHashFull sha256=4d5343ac24042fd03f0cc5959a9867b5902aee4725c5a75775cd711aae76b709
    Hash choices=sha256
    CMSDigest=4d5343ac24042fd03f0cc5959a9867b5902aee4725c5a75775cd711aae76b709
    CMSDigestType=2
    Launch Constraints:
        None
    CDHash=4d5343ac24042fd03f0cc5959a9867b5902aee47