Skip to content

macOS Code Signing and Notarization#669

Merged
halprin merged 19 commits intoDescentDevelopers:mainfrom
halprin:macos-code-sign
Feb 27, 2025
Merged

macOS Code Signing and Notarization#669
halprin merged 19 commits intoDescentDevelopers:mainfrom
halprin:macos-code-sign

Conversation

@halprin
Copy link
Member

@halprin halprin commented Feb 24, 2025

Pull Request Type

  • GitHub Workflow changes
  • Documentation or Wiki changes
  • Build and Dependency changes
  • Runtime changes
    • Render changes
    • Audio changes
    • Input changes
    • Network changes
    • Other changes

Description

The macOS binaries are now code signed and notarized.

There are two ways this is accomplished.

  1. The hog files that contain dynamic libraries. These libraries must be signed before packaging into the hog file (because you can't just sign the hog file), so they are signed during the build. This includes d3-osx.hog and everything in the online folder.
  2. The main application and raw binaries in the netgames folder. These binaries are signed after the build using a helper GitHub action. This helper GitHub action also packages everything into a DMG disk image and notarizes the disk image. A DMG is an established package format and natively supports notarization, unlike a zip file.

Code signing and notarization only happens on push to the main branch, not in a pull request. Secrets can't be accessed in a pull request from a fork, but I also chose to refactor the GitHub action workflows to explicitly not pass in secrets into the pull request workflow to make it obvious. Let me know if we don't like this, but I thought the explicit approach was better than implicit. We can also leverage this approach in the future for versioned releases.

Updated the documentation to remove the work-arounds needed when the application was unsigned.

If you want to inspect a test build, you can see this build in my fork. You can't depend on the build on this PR because, as mentioned above, we aren't signing and notarizing for PR builds.

Related Issues

Fixes #540.
Fixes #559.

Screenshots (if applicable)

Checklist

  • I have tested my changes locally and verified that they work as intended.
  • I have documented any new or modified functionality.
  • I have reviewed the changes to ensure they do not introduce any unnecessary complexity or duplicate code.
  • I understand that by submitting this pull request, I am agreeing to license my contributions under the project's license.

Additional Comments

@Lgt2x Lgt2x requested review from Lgt2x and tophyr February 24, 2025 07:42
Copy link
Contributor

@tophyr tophyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, thank you THANK YOU!!! This will really help the user experience on Mac. I noted some threat-model risks that we should mitigate, but I don't think they're critical to do before merging this.

runs-on: ${{ matrix.os.runner }}

env:
MACOS_SIGNING: ${{ secrets.MACOS_SIGNING_IDENTITY != '' }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might be better to include the os preset check in here as well? then the if clauses in the following changes could check just one master variable. thoughts?

Suggested change
MACOS_SIGNING: ${{ secrets.MACOS_SIGNING_IDENTITY != '' }}
MACOS_SIGNING: ${{ matrix.os.preset == 'mac' && secrets.MACOS_SIGNING_IDENTITY != '' }}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't lump them together because sometimes we need to build for non-Mac, build for the Mac but not sign, and build for the Mac and sign. If we combined the variables, then we couldn't sometimes build for the Mac but not sign or sometimes build for the Mac and sign.

Copy link
Contributor

@tophyr tophyr Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would just be MACOS_SIGNING=false in those cases, wouldn't it? I'm just noticing that all three times MACOS_SIGNING is referenced, it's also &&'ed with the os.preset == 'mac' check. If we move the os.preset == 'mac' check into the MACOS_SIGNING variable, we can remove it from the three usage sites and prevent mixups in the future if someone alters this workflow to add a new usage but forgets to check the OS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. Hmmm. But even with this change we would continue to use if: ${{ matrix.os.preset == 'mac' }} like when we call Install macOS Rosetta 2 and a couple others places, right?

Copy link
Contributor

@tophyr tophyr Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. Hmmm. But even with this change we would continue to use if: ${{ matrix.os.preset == 'mac' }} like when we call Install macOS Rosetta 2 and a couple others places, right?

I would imagine, yeah. Adding the check here would just be for fully-encapsulating the standalone answer to "should i perform mac signing"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

build:
name: Build for PR
uses: ./.github/workflows/build.yml
# explicitly not passing secrets into the build
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double-checking: pull requests originating from outside the Descent3 repository will never get access to secrets, no matter what - correct? ie, even if the PR modifies this file, or even if someone merges a modification to this file?

(is it even possible to submit a PR to yourself? suppose i could try that out, but kinda irrelevant either way)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do an actual live test of this tomorrow.

Copy link
Member Author

@halprin halprin Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The live test was a success. I confirmed GitHub's documentation to be correct. A PR from a fork has no access to the secrets in the base repository. Even if the fork PR changes the GitHub Action and tries to reference the secrets and exfiltrate them, nothing is accessed.

Copy link
Member

@Lgt2x Lgt2x left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested your artifact, it works great! That's some seriously cool work you did there :)

I invited you to the github org so you can push the secrets you need

CC: ${{ matrix.os.cc }}
CXX: ${{ matrix.os.cxx }}
run: cmake --preset ${{ matrix.os.preset }} -DBUILD_TESTING=ON -DENABLE_LOGGER=ON -DFORCE_PORTABLE_INSTALL=ON -DBUILD_EDITOR=ON -DUSE_EXTERNAL_PLOG=ON
run: cmake --preset ${{ matrix.os.preset }} -DCODESIGN_IDENTITY=${{ secrets.MACOS_SIGNING_IDENTITY }} -DBUILD_TESTING=ON -DENABLE_LOGGER=ON -DFORCE_PORTABLE_INSTALL=ON -DBUILD_EDITOR=ON -DUSE_EXTERNAL_PLOG=ON
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CODESIGN_IDENTITY CMake cache variable should be declared as such in the top-level CMakeLists.txt: set(CODESIGN_IDENTITY ... CACHE STRING ...) like we do for USE_VCPKG for example. It should also be documented in BUILD.md

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. CMake isn't my forte, so let me know if I can do this better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooo, maybe not fixed. In fact, the build is failing. Sad.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried futzing with it but couldn't get it working. I'll try again tomorrow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into it tonight and try to help you out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see suggestions below

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the help! Fixed.

target_include_directories(Descent3_Online_TCP_IP PRIVATE ${SDL3_INCLUDE_DIRS})
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set_target_properties(Descent3_Online_TCP_IP PROPERTIES SUFFIX ".dylib")
if(DEFINED CODESIGN_IDENTITY AND NOT "${CODESIGN_IDENTITY}" STREQUAL "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make that couple lines a CMake function or macro that takes the target name as argument

Copy link
Member

@Lgt2x Lgt2x Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can define a function like this in the top level CMakelists:

set(CODESIGN_IDENTITY "" CACHE STRING "Sets the macOS code signing identity. If set to something besides the empty string, then the dynamic libraries put into the hog files will be signed using this identity.")
function (macos_sign target)
  message ("target: ${target}")
  if(DEFINED CODESIGN_IDENTITY AND NOT "${CODESIGN_IDENTITY}" STREQUAL "")
  message(STATUS "Code signing ${target}")
  add_custom_command(TARGET ${target} POST_BUILD
         COMMAND codesign --verbose --sign "${CODESIGN_IDENTITY}" --force --timestamp --deep -o runtime $<TARGET_FILE:${target}>)
  endif()  
endfunction()

and use it as macos_sign("Parallax_Online") for instance

I hope this helps

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the help! Fixed.

Copy link
Contributor

@tophyr tophyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMakeLists.txt Outdated
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "default build type")
endif()

set(CODESIGN_IDENTITY "" CACHE STRING "Sets the macOS code signing identity. If set to something besides the empty string "", then the dynamic libraries put into the hog files will be signed using this identity.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not work because of the double quotes inside of the documentation string "", CMake does not like that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the help there!

@Lgt2x
Copy link
Member

Lgt2x commented Feb 27, 2025

Good for me, are the secrets all set on this repository so we can merge @halprin ?

@halprin
Copy link
Member Author

halprin commented Feb 27, 2025

We're ready to merge!

@halprin halprin merged commit d48b0f1 into DescentDevelopers:main Feb 27, 2025
10 checks passed
@halprin halprin deleted the macos-code-sign branch February 27, 2025 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants