Mac OS app signing without Fastlane

2.0
macos

#1

We are building Electron app on Linux and Mac executors on Circle and everything goes well until we try to sign Mac app. We are using “electron-osx-sign” NPM package, but it requires a developer profile to be already installed. On our current non-CircleCI Mac machine we have installed the identity into keychain maually and see it using “security find-identity -v -p codesigning” and see “1 valid identities found”

Now we are trying to do the same on CircleCI Mac. After looking at multiple issues here and on Fastlane I cannot see any solution for Mac signing - only that iOS apps might be signed using Fastlane. Is there any way to avoid Fastlane (that does not support Mac app signing) and set up our profile for signing our application?


#2

Hi @bahmutov - CircleCI only supports Fastlane for codesigning, which does mean only for iOS apps. However, others have found ways around this and this article may be helpful in this use case.

If you have a moment, please add support for alternate codesigning to our Ideas page. Adding ideas, votes and comments here will help us prioritize new and improved features.


#3

Thank you Sara for suggesting that article. It did help me understand how to import profile file. But we have noticed that importing the profile on Circle is extremely flaky. For example, here is our script that we run to import the profile from environment variable into keychain

echo $Certificates | base64 -D -o Certificates.p12
sudo -E security import ./Certificates.p12 -P $P12_PASSWORD

The above command runs maybe once in 3 times - without any code changes in between. Sometimes rerunning the workflow that failed on import fixes it! The error is

security: SecKeychainItemImport: User interaction is not allowed.

We have a private project that shows the random success and failures: https://circleci.com/gh/cypress-io/workflows/try-mac-sign-on-circle/tree/master The entire config file is below, we have even tried rerunning import several times - but that does not help.

version: 2.1

# https://circleci.com/docs/2.0/configuration-reference/

codesign: &codesign
  environment:
    FASTLANE_LANE: all
  name: Set up code sign (if on Mac)
  # command: sudo -E ./set-mac-codesign.sh
  command: |
    set -e

    echo "In shell script hello is $HELLO"

    if [[ "$OSTYPE" == "darwin"* ]]; then

      echo "This shell scripts imports Mac code sign profile from environment variable"
      echo "Assumes the certificate is base64 encoded as environment variable Certificates"
      echo "and its password is in environment variable P12_PASSWORD"
      echo "should be run with sudo"
      echo ""

      if [ -z "$Certificates" ]; then
        echo "Need to provide environment variable Certificates"
        exit 1
      fi
      if [ -z "$P12_PASSWORD" ]; then
        echo "Need to provide environment variable P12_PASSWORD"
        exit 1
      fi

      echo $Certificates | base64 -D -o Certificates.p12
      sudo -E security import ./Certificates.p12 -P $P12_PASSWORD

      # check
      security list-keychains
      security find-identity -v -p codesigning
    else
      echo "Not Mac platform, skipping code sign setup"
    fi

# certificates and commands from
# https://gist.github.com/silvinaroldan/5c7e4946685f637bfdeaea25ef8eee2c
jobs:
  show-identity:
    macos:
      xcode: "10.1.0"
    shell: /bin/bash --login
    steps:
      - checkout

      - run: echo "Hello is $HELLO"

      - run: node -v
      - run: npm -v

      - run:
          <<: *codesign

      - run:
          <<: *codesign
          when: on_fail

      - run:
          <<: *codesign
          when: on_fail

      - run:
          <<: *codesign
          when: on_fail

      - run:
          <<: *codesign
          when: on_fail

      - run:
          <<: *codesign
          when: on_fail

      - run: ls -la

      - run: rm Certificates.p12

      - run:
          name: Show keychains
          command: security list-keychains
      - run:
          name: Find code signing identity
          command: security find-identity -v -p codesigning

workflows:
  mac:
    jobs:
      - show-identity:
          context: org-global

Is there anything you can do to help us, please


#4

ok, by following advice in https://stackoverflow.com/questions/16550594/jenkins-xcode-build-works-codesign-fails we could create custom keychain and switch to it - and that seems to always work, even if we could not successfully sign the app yet. Will consider this solved for now.


#5

Hi @bahmutov, would you consider sharing your config.yml (minus any sensitive info, obviously), a link to your repo, and/or steps to recreate this solution?

We have exactly the same situation: an Electron app building on a Linux executor for Windows and Linux apps/installers and a Mac executor for the OS X app and installer. I have everything working on the macos executor up to the signing step. I have been banging my head on this last part for a couple days now. I’ll start trying to implement the steps from the SO post but having something to look at that I know works would be extremely helpful.

@Sara and CircleCI generally, this would be great thing to capture in the official documentation. Signing cross-platform apps for OS X compatibility is difficult out of the box because of Apple’s super annoying Apple-only signing process, so making it easier to accomplish would be amazingly useful!


#6

BTW, I added “Simplify import of signing certificates on macos executors” per @Sara’s suggestion, so upvotes and comments on that may be helpful to get this streamlined and better integrated into the build process.


#7

Sure, here is my shell file, and I have been playing with this locally on my mac to see what is blocking the codesign. It is always popping “enter keychain password” modal, even when the keychain is unlocked … See dialog in https://github.com/cypress-io/cypress/issues/2958#issuecomment-452045504 Extremely frustrating, since we cannot sign our Cypress.io test runner on Mac that we build on CircleCI - always have to maintain a separate Mac box just to do the codesign. On separate Mac machine we can go, click “Always Allow” and the certificate is allowed to be used to sign during `codesign step.

echo $Certificates | base64 -D -o Certificates.p12

echo "Creating keychain"

security create-keychain -p default MyKeychain.keychain

echo "unlocking keychain"

security set-keychain-settings MyKeychain.keychain

security unlock-keychain -p default MyKeychain.keychain

echo "Importing certificates"

# see security import --help

# -A allows every app to access the certificate

# -T "path" allows only app at path to access the certificate

# -x private keys should not be extractable after import

# security import ./Certificates.p12 -k MyKeychain.keychain -A -P $P12_PASSWORD

# security import ./Certificates.p12 -t agg -k MyKeychain.keychain -P $P12_PASSWORD -T /usr/bin/codesign

security import ./Certificates.p12 -x -t agg -k MyKeychain.keychain -A -P $P12_PASSWORD

# we don't need the certificate file anymore

rm Certificates.p12

# check

echo "appending new keychain to the list"

security list-keychains -d user -s MyKeychain.keychain $(security list-keychains -d user | sed s/\"//g)

security list-keychains

echo "setting default keychain"

security default-keychain -s MyKeychain.keychain

echo "finding identity"

security find-identity -v -p codesigning

# if need to delete the new keychain

# security delete-keychain MyKeychain.keychain

Code sign command

codesign --deep --force --verbose --sign <identity id number> build/darwin/Cypress.app/


#8

@bahmutov, thanks for the info. I was running into the same issue with the keychain password when I ran locally but strangely my app was getting signed on the CircleCI build. The signature was invalid and I couldn’t open the DMG but it was signed. Which was really weird.

Then I realized that it was getting signed using the certificate that we use to sign our Windows app/installer. And once I realized that I figured everything out! Well, not everything, but at least what I needed to know to get signed Electron apps for Windows and Mac out of CircleCI.

So I now have the process to build signed installers/apps for Windows, Linux, and OS X working. The real trick is that you don’t need to do any of the keychain stuff. It’s actually much simpler than that.

For reference, you can check out my working config.yml.

The trick is base64-encoding the application certificate (i.e. Developer ID Application: Foobar, LLC) and setting that in the context referenced in your configuration. My config has the following workflow defined:

workflows:
  version: 2
  build_all:
    jobs:
      - build_win_and_linux:
          context: dxm
      - build_mac:
          context: dxm

I have three variables set in that dxm context:

  • CSC_LINK contains the base64-encoded Apple application certificate
  • CSC_KEY_PASSWORD is set to the password for the certificate in CSC_LINK
  • WIN_CSC_LINK contains a base64-encoded certificate from DigiCert

And that’s basically it. Those environment variables get picked up by electron-osx-sign and converted for use when signing the applications/installers.

I’ll try to document the procedure from start to finish later but if anyone from CircleCI wants to try do it first feel free to reach out if you need any info.


#9

@rherrick wow, this would be super nice way to sign - but I need to switch from using electron-osx-sign to electron-builder for signing (the builder supports environment variables CSC_LINK etc) for signing. I am trying it right now


#10

@rherrick I am looking at the source code for electron-builder and it does call electron-osx-sign, if only I saw all the options there, ughh https://github.com/electron-userland/electron-builder/blob/e59f6c3b6155534fe2076fc1b8a0ba0bbc348f62/packages/app-builder-lib/src/macPackager.ts#L241


#11

@rherrick can I ask you to run successful mac build with DEBUG=electron-builder,electron-osx-sign* environment variables to see what the osx-sign does? Please, this would help us move past this problem


#12

I have used electron-builder to build prepacked app - and it successfully signs the app on CircleCI https://github.com/cypress-io/cypress/issues/2958 - and there was failure after that, I am investigating, but this seems so so close now.


#13

just final success note: I could disable additional things electron-builder does and just make it do the code signing correctly (using environment variables) with command build --publish never --prepackaged <appFolder> Super happy, and thanks a lot @rherrick for pointing me on the right path. I owe you a beer, assuming you like it.


#14

Awesome, I’m glad you were able to get it work! I actually just put the final touches on our build configuration as well (including pushing artifacts to the app’s download page and prompting auto-updates on production releases!). If anyone’s curious to have a look, check out the application repository.

Also, we’re using the node-java package so we can use various DICOM parsing and anonymization libraries, so we also needed a custom Docker image for the Windows and Linux build because of weird version dependencies and requirements. That may be of interest to others as well, so have a look at that repo if so.

And I love beer, so all we need is the opportunity :smile: :beers:


closed #15

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.