Search code examples
macoscertificategithub-actionssignnotarize

How to sign and notarize a PKG within a Github Actions macos runner


Context

I'm building a Github Actions job to build, sign and notarize a PKG file.

I'm using an Apple Id account (the workflow needs username and password) as well as a Developer Id Installer certificate with private key (encrypted). Both are saved as secrets (base64) and will be converted in the workflow to a .p12 file, then added to keychain.

This job is part of a bigger workflow in a private repository, that generates first the files (using Pyinstaller) from the software, and then export the PKG on a AWS S3 Bucket.

The implementation

jobs:
  [...]

  pkg:
    needs: [...]
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Download macos bin file
        uses: actions/download-artifact@v2
        with:
          name: macos-bin-files
          path: dist/
      - name:
        run: | 
          ----- Create certificate files from secrets base64 -----
          echo ${{ secrets.DEVELOPER_ID_INSTALLER_CER }} | base64 --decode > certificate_installer.cer
          echo ${{ secrets.DEVELOPER_ID_INSTALLER_KEY }} | base64 --decode > certificate_installer.key

          ----- Create p12 file -----
          openssl pkcs12 -export -name zup -in certificate_installer.cer -inkey certificate_installer.key -passin pass:${{ secrets.KEY_PASSWORD }} -out certificate_installer.p12 -passout pass:${{ secrets.P12_PASSWORD }}

          ----- Configure Keychain -----
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH

          ----- Import certificates on Keychain -----
          security import certificate_installer.p12 -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH

          ----- Generate PKG from files -----
          use a macos installer script (ref: https://github.com/KosalaHerath/macos-installer-builder/tree/master/macOS-x64)

          ----- Sign PKG file -----
          productsign --sign "${{ secrets.DEVELOPER_ID_INSTALLER_NAME }}" $INPUT_FILE_PATH $OUTPUT_FILE_PATH

          - name: "Notarize Release Build PKG"
            uses: devbotsxyz/xcode-notarize@v1 
            with:
              product-path: $PATH_TO_PKG
              appstore-connect-username: ${{ secrets.APPLE_ACCOUNT_USERNAME }}
              appstore-connect-password: ${{ secrets.APPLE_ACCOUNT_PASSWORD }}
              primary-bundle-id: 'BUNDLE_ID'

          - name: "Staple Release Build"
            uses: devbotsxyz/xcode-staple@v1
            with:
              product-path: $PATH_TO_PKG

  [...]

Issue

I followed the Github Action official documentation for installing an Apple certificate on macOS runners and this part is working as expected. I can add the certificate to keychain and use it to sign the PKG file with the productsign command.

However, when I send the PKG to Apple for notarization, it returns me that error:

Error: Notarization status <invalid> - Package Invalid
Error: Notarization failed

What I tried

The PKG is working as expected when distributed (just need to be opened as admin as not notarized), so the problem doesn't seem related to the package implementation.

I tried to perform the notarization with command lines, following links from different sources:

However, even with those command lines (without using the notarize action), I couldn't notarize the PKG file.

Question

What is wrong with my workflow, did I miss something before trying to notarize the package?

PS: I couldn't find any reference doing this on a Github Actions macos runner...


Solution

  • Solution

    After setting the devbotsxyz/xcode-notarize@v1 field verbose:true, I observed that Apple returns a link to a JSON explaining why the package is INVALID for notarization.

    In this JSON, it was informed that all the files composing my PKG (there are many as I couldn't use --onefile with Pyinstaller in my context) were invalid because they weren't signed and timestamped.

    After some researches, I found this post on the Apple Developer Forum and understood that PKG files need to be signed INSIDE OUT: First, you sign each file generated by Pyinstaller, then sign the PKG gathering all those files.

    To do so, you can't use the productsign command (as it only work for .pkg, .zip and .dmg) but codesign.

    However, codesign doesn't use a Developer Id Installer certificate, but a Developer Id Application certificate, so I had to add this new certificate to the workflow as well.

    Note that as my PKG would be composed of hundreds of files, I also needed a bash script to use codesign to sign each one of them.

    My final workflow looks like this:

    jobs:
      [...]
    
      pkg:
        needs: [...]
        runs-on: macos-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Download macos bin file
            uses: actions/download-artifact@v4
            with:
              name: macos-bin-files
              path: dist/
          - name:
            run: | 
              ----- Create certificate files from secrets base64 -----
              echo ${{ secrets.DEVELOPER_ID_INSTALLER_CER }} | base64 --decode > certificate_installer.cer
              echo ${{ secrets.DEVELOPER_ID_INSTALLER_KEY }} | base64 --decode > certificate_installer.key
              echo ${{ secrets.DEVELOPER_ID_APPLICATION_CER }} | base64 --decode > certificate_application.cer
              echo ${{ secrets.DEVELOPER_ID_APPLICATION_KEY }} | base64 --decode > certificate_application.key
    
              ----- Create p12 file -----
              openssl pkcs12 -export -name zup -in certificate_installer.cer -inkey certificate_installer.key -passin pass:${{ secrets.KEY_PASSWORD }} -out certificate_installer.p12 -passout pass:${{ secrets.P12_PASSWORD }}
              openssl pkcs12 -export -name zup -in certificate_application.cer -inkey certificate_application.key -passin pass:${{ secrets.KEY_PASSWORD }} -out certificate_application.p12 -passout pass:${{ secrets.P12_PASSWORD }}
    
              ----- Configure Keychain -----
              KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
              security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
              security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
              security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
    
              ----- Import certificates on Keychain -----
              security import certificate_installer.p12 -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
              security import certificate_application.p12 -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
              security list-keychain -d user -s $KEYCHAIN_PATH
    
              ----- Codesign files with script -----
              use a script to sign each file from the artifact (ref: https://gist.github.com/GuillaumeFalourd/4efc73f1a6014b791c0ef223a023520a)
    
              ----- Generate PKG from codesigned files -----
              use a macos installer script (ref: https://github.com/KosalaHerath/macos-installer-builder/tree/master/macOS-x64)
    
              ----- Sign PKG file -----
              productsign --sign "${{ secrets.DEVELOPER_ID_INSTALLER_NAME }}" $INPUT_FILE_PATH $OUTPUT_FILE_PATH
    
              - name: Notarize Release Build PKG
                uses: lando/notarize-action@v2
                with:
                  product-path: $PATH_TO_PKG
                  appstore-connect-username: ${{ secrets.APPLE_ACCOUNT_USERNAME }}
                  appstore-connect-password: ${{ secrets.APPLE_ACCOUNT_PASSWORD }}
        appstore-connect-team-id: FY8GAUX283 # if using notarytool
    
              - name: "Staple Release Build"
                uses: devbotsxyz/xcode-staple@v1
                with:
                  product-path: $PATH_TO_PKG
    
      [...]
    

    EDIT: Notary tools have been updated on Apple. Previously the action below was used in the process but has been removed from the marketplace:

    - name: "Notarize Release Build PKG"
      uses: devbotsxyz/xcode-notarize@v1 
      with:
       product-path: $PATH_TO_PKG
       appstore-connect-username: ${{ secrets.APPLE_ACCOUNT_USERNAME }}
       appstore-connect-password: ${{ secrets.APPLE_ACCOUNT_PASSWORD }}
       primary-bundle-id: 'BUNDLE_ID'
    

    I created this one to substitute the notarize and staple actions:

    - name: "Notarize and Staple Release Build"
      uses: GuillaumeFalourd/notary-tools@v1
      with:
        product_path: "$PATH_TO_PKG_OR_APP"
        apple_id: ${{ secrets.NOTARIZATION_USERNAME }}
        password: ${{ secrets.NOTARIZATION_PASSWORD }}
        team_id: ${{ secrets.NOTARIZATION_TEAMID }}
        staple: true
        xcode_path: '/Applications/Xcode.app'