From f6d8c1cea1c294bd75c746254ef78653db24593c Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 12:53:41 -0700 Subject: [PATCH 01/12] Add Apple codesigning and notarization to nightly builder --- .github/workflows/nightly.yaml | 57 ++++++++++++++++++++++++++++++++-- macos_sign_and_notarize.py | 13 ++++---- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 75c0b37..01802f6 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -150,7 +150,7 @@ jobs: with: artichoke_ref: ${{ steps.release_info.outputs.commit }} target_triple: ${{ matrix.target }} - output_file: ${{ github.workspace }}/THIRDPARTY + output_file: ${{ github.workspace }}/THIRDPARTY.txt - name: Clone Artichoke uses: actions/checkout@v3 @@ -208,14 +208,65 @@ jobs: working-directory: artichoke run: cargo build --verbose --release --target ${{ matrix.target }} + # This will codesign binaries in place which means that the tarballed + # binaries will be codesigned as well. + - name: Run Apple Codesigning and Notarization + id: apple_codesigning + if: runner.os == 'macOS' + run: | + python3 macos_sign_and_notarize.py "artichoke-nightly-${{ matrix.target }}" \ + --binary artichoke/target/release/artichoke \ + --binary artichoke/target/release/airb \ + --resource artichoke/LICENSE \ + --resource artichoke/README.md \ + --resource THIRDPARTY.txt + env: + MACOS_NOTARIZE_APP_PASSWORD: ${{ secrets.MACOS_NOTARIZE_APP_PASSWORD }} + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PASSPHRASE: ${{ secrets.MACOS_CERTIFICATE_PASSPHRASE }} + + - name: GPG sign Apple DMG + id: apple_codesigning_gpg + if: runner.os == 'macOS' + run: | + python3 gpg_sign.py "artichoke-nightly-${{ matrix.target }}" \ + --artifact "${{ steps.apple_codesigning.outputs.asset }}" + + - name: Upload release archive + uses: ncipollo/release-action@v1 + if: runner.os == 'macOS' + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release_info.outputs.version }} + draft: true + allowUpdates: true + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + omitPrereleaseDuringUpdate: true + artifacts: ${{ steps.apple_codesigning.outputs.asset }} + artifactContentType: ${{ steps.apple_codesigning.outputs.content_type }} + + - name: Upload release signature + uses: ncipollo/release-action@v1 + if: runner.os == 'macOS' + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release_info.outputs.version }} + draft: true + allowUpdates: true + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + omitPrereleaseDuringUpdate: true + artifacts: ${{ steps.apple_codesigning_gpg.outputs.signature }} + artifactContentType: "text/plain" + - name: Build archive shell: bash id: build run: | staging="artichoke-nightly-${{ matrix.target }}" mkdir -p "$staging"/ - cp artichoke/{README.md,LICENSE} "$staging/" - cp THIRDPARTY "$staging/THIRDPARTY.txt" + cp artichoke/{README.md,LICENSE} THIRDPARTY.txt "$staging/" if [ "${{ runner.os }}" = "Windows" ]; then cp "artichoke/target/${{ matrix.target }}/release/artichoke.exe" "$staging/" cp "artichoke/target/${{ matrix.target }}/release/airb.exe" "$staging/" diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index 418c1ac..fc60076 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -176,9 +176,9 @@ def notarization_app_specific_password(): codesigning identity's Apple ID. """ - if app_specific_password := os.getenv("APPLE_ID_APP_PASSWORD"): + if app_specific_password := os.getenv("MACOS_NOTARIZE_APP_PASSWORD"): return app_specific_password - raise Exception("APPLE_ID_APP_PASSWORD environment variable is required") + raise Exception("MACOS_NOTARIZE_APP_PASSWORD environment variable is required") def notarization_team_id(): @@ -281,7 +281,7 @@ def import_notarization_credentials(): # xcrun notarytool store-credentials \ # "$notarytool_credentials_profile" \ # --apple-id "apple-codesign@artichokeruby.org" \ - # --password "$APPLE_ID_APP_PASSWORD" \ + # --password "$MACOS_NOTARIZE_APP_PASSWORD" \ # --team-id "VDKP67932G" \ # --keychain "$keychain_path" subprocess.run( @@ -326,10 +326,10 @@ def import_codesigning_certificate(): except binascii.Error: raise Exception("MACOS_CERTIFICATE must be base64 encoded") - certificate_password = os.getenv("MACOS_CERTIFICATE_PWD") + certificate_password = os.getenv("MACOS_CERTIFICATE_PASSPHRASE") if not certificate_password: raise Exception( - "MACOS_CERTIFICATE_PASSWORD environment variable is required" + "MACOS_CERTIFICATE_PASSPHRASE environment variable is required" ) with tempfile.TemporaryDirectory() as tempdirname: @@ -754,7 +754,8 @@ def main(args): staple_bundle(bundle=bundle) validate(bundle=bundle, binary_names=[binary.name for binary in binaries]) - set_output(name="bundle", value=bundle) + set_output(name="asset", value=bundle) + set_output(name="content_type", value="application/x-apple-diskimage") return 0 except subprocess.CalledProcessError as e: From db57e98aaf754885b24af12044599e86f30be72d Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 13:12:26 -0700 Subject: [PATCH 02/12] Improve error reporting in codesigning scripts - Fix a missing f-string. - Always print traceback on exceptions. - Print traceback to stderr. --- gpg_sign.py | 5 +++-- macos_sign_and_notarize.py | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gpg_sign.py b/gpg_sign.py index 60dc073..d707b4c 100755 --- a/gpg_sign.py +++ b/gpg_sign.py @@ -160,7 +160,7 @@ def main(args): for artifact in artifacts: if not artifact.is_file(): - print("Error: {artifact} does not exist", file=sys.stderr) + print(f"Error: artifact file {artifact} does not exist", file=sys.stderr) return 1 if len(artifacts) > 1: @@ -191,10 +191,11 @@ def main(args): \tReturn Code: {e.returncode}""", file=sys.stderr, ) + print(traceback.format_exc(), file=sys.stderr) return e.returncode except Exception as e: print(f"Error: {e}", file=sys.stderr) - print(traceback.format_exc()) + print(traceback.format_exc(), file=sys.stderr) return 1 diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index fc60076..407468f 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -733,7 +733,11 @@ def main(args): for binary in binaries: if not binary.is_file(): - print("Error: {binary} does not exist", file=sys.stderr) + print(f"Error: binary file {binary} does not exist", file=sys.stderr) + return 1 + for resource in resources: + if not resource.is_file(): + print(f"Error: resource file {resource} does not exist", file=sys.stderr) return 1 try: @@ -765,10 +769,11 @@ def main(args): \tReturn Code: {e.returncode}""", file=sys.stderr, ) + print(traceback.format_exc(), file=sys.stderr) return e.returncode except Exception as e: print(f"Error: {e}", file=sys.stderr) - print(traceback.format_exc()) + print(traceback.format_exc(), file=sys.stderr) return 1 finally: # Purge keychain. From 635b576b25dd0cf9d09602b5dfef984a51cb4396 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 13:13:06 -0700 Subject: [PATCH 03/12] Use correct binary path for macOS codesigning --- .github/workflows/nightly.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 01802f6..031abc3 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -215,8 +215,8 @@ jobs: if: runner.os == 'macOS' run: | python3 macos_sign_and_notarize.py "artichoke-nightly-${{ matrix.target }}" \ - --binary artichoke/target/release/artichoke \ - --binary artichoke/target/release/airb \ + --binary "artichoke/target/${{ matrix.target }}/release/artichoke" \ + --binary "artichoke/target/${{ matrix.target }}/release/airb" \ --resource artichoke/LICENSE \ --resource artichoke/README.md \ --resource THIRDPARTY.txt From d3bb414435d8bcf85a50a32ebf460c673b56aabf Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 14:21:27 -0700 Subject: [PATCH 04/12] Raise if GPG signing fails --- gpg_sign.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gpg_sign.py b/gpg_sign.py index d707b4c..0999d4a 100755 --- a/gpg_sign.py +++ b/gpg_sign.py @@ -92,6 +92,7 @@ def gpg_sign_artifact(*, artifact_path, release_name): str(asc), str(artifact_path), ], + check=True, # capture output because `gpg --detatch-sign` writes to stderr which # prevents the GitHub Actions log group from working correctly. stdout=subprocess.PIPE, @@ -120,6 +121,7 @@ def validate(*, artifact_name, asc): str(asc), str(artifact_name), ], + check=True, # capture output because `gpg --verify` writes to stderr which # prevents the GitHub Actions log group from working correctly. stdout=subprocess.PIPE, From 9fdaf73e594b83e048d9f85bda6b89e1b3ad546a Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 14:21:52 -0700 Subject: [PATCH 05/12] Improve logging in macOS codesigning script Redirect stderr to stdout so the logs appear within the log groups. --- macos_sign_and_notarize.py | 89 +++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index 407468f..f870793 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -218,7 +218,7 @@ def create_keychain(*, keychain_password): with log_group("Setup notarization keychain"): # security create-keychain -p "$keychain_password" "$keychain_path" - subprocess.run( + proc = subprocess.run( [ "security", "create-keychain", @@ -227,14 +227,24 @@ def create_keychain(*, keychain_password): str(keychain_path()), ], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) # security set-keychain-settings -lut 900 "$keychain_path" - subprocess.run( + proc = subprocess.run( ["security", "set-keychain-settings", "-lut", "900", str(keychain_path())], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) # security unlock-keychain -p "$keychain_password" "$keychain_path" - subprocess.run( + proc = subprocess.run( [ "security", "unlock-keychain", @@ -243,7 +253,12 @@ def create_keychain(*, keychain_password): str(keychain_path()), ], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) def delete_keychain(): @@ -256,14 +271,20 @@ def delete_keychain(): """ with log_group("Delete keychain"): - try: - # security delete-keychain /path/to/notarization.keychain-db - subprocess.run( - ["security", "delete-keychain", str(keychain_path())], - check=True, - ) + # security delete-keychain /path/to/notarization.keychain-db + proc = subprocess.run( + ["security", "delete-keychain", str(keychain_path())], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) + print() + + if proc.returncode == 0: print(f"Keychain deleted from {keychain_path()}") - except subprocess.CalledProcessError: + else: # keychain does not exist print(f"Keychain not found at {keychain_path()}, ignoring ...") @@ -284,7 +305,7 @@ def import_notarization_credentials(): # --password "$MACOS_NOTARIZE_APP_PASSWORD" \ # --team-id "VDKP67932G" \ # --keychain "$keychain_path" - subprocess.run( + proc = subprocess.run( [ "/usr/bin/xcrun", "notarytool", @@ -300,7 +321,12 @@ def import_notarization_credentials(): str(keychain_path()), ], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) def import_codesigning_certificate(): @@ -339,7 +365,7 @@ def import_codesigning_certificate(): # -k "$keychain_path" \ # -P "$MACOS_CERTIFICATE_PWD" \ # -T /usr/bin/codesign - subprocess.run( + proc = subprocess.run( [ "security", "import", @@ -352,7 +378,29 @@ def import_codesigning_certificate(): "/usr/bin/codesign", ], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) + + proc = subprocess.run( + [ + "security", + "find-identity", + "-p", + "codesigning", + "-v", + str(keychain_path()), + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) def setup_codesigning_and_notarization_keychain(*, keychain_password): @@ -372,7 +420,7 @@ def setup_codesigning_and_notarization_keychain(*, keychain_password): # security set-key-partition-list \ # -S "apple-tool:,apple:,codesign:" \ # -s -k "$keychain_password" "$keychain_path" - subprocess.run( + proc = subprocess.run( [ "security", "set-key-partition-list", @@ -384,7 +432,12 @@ def setup_codesigning_and_notarization_keychain(*, keychain_password): str(keychain_path()), ], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) def codesign_binary(*, binary_path): @@ -402,7 +455,7 @@ def codesign_binary(*, binary_path): # --force \ # "$binary_path" with log_group(f"Run codesigning [{binary_path.name}]"): - subprocess.run( + proc = subprocess.run( [ "/usr/bin/codesign", "--keychain", @@ -420,7 +473,12 @@ def codesign_binary(*, binary_path): str(binary_path), ], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, ) + for line in proc.stdout.splitlines(): + print(line) def create_notarization_bundle(*, release_name, binaries, resources): @@ -777,7 +835,8 @@ def main(args): return 1 finally: # Purge keychain. - delete_keychain() + # delete_keychain() + pass if __name__ == "__main__": From 72b44e7cfe62b01c4f93b9b671c6784862a2c917 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 14:23:16 -0700 Subject: [PATCH 06/12] Ignore keychain files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2b8c6cf..d7cee9e 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,4 @@ build-iPhoneSimulator/ *.dmg /dist/* +*.keychain-db From e129855406497d813b496f8d437ea1b016ec177b Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 14:35:33 -0700 Subject: [PATCH 07/12] Use hash code as codesigning identity --- macos_sign_and_notarize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index f870793..7064de4 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -159,7 +159,7 @@ def codesigning_identity(): Codesigning identity and name of the Apple Developer ID Application. """ - return "Developer ID Application: Ryan Lopopolo (VDKP67932G)" + return "230CC499559E4A43CFCCD853B938C17E69C5ECB6" def notarization_apple_id(): From 879f0fa8e69ce68fb3ebd0e35ac3638b6b5f6dc8 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 14:50:10 -0700 Subject: [PATCH 08/12] Add more verbose error logging for subprocess.CalledProcessError --- gpg_sign.py | 20 ++++++++++++++------ macos_sign_and_notarize.py | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/gpg_sign.py b/gpg_sign.py index 0999d4a..ea0459c 100755 --- a/gpg_sign.py +++ b/gpg_sign.py @@ -187,12 +187,20 @@ def main(args): return 0 except subprocess.CalledProcessError as e: - print( - f"""Error: failed to invoke command. - \tCommand: {e.cmd} - \tReturn Code: {e.returncode}""", - file=sys.stderr, - ) + print("Error: failed to invoke command", file=sys.stderr) + print(f" Command: {e.cmd}", file=sys.stderr) + print(f" Return Code: {e.returncode}", file=sys.stderr) + if e.stdout: + print() + print("Output:", file=sys.stderr) + for line in e.stdout.splitlines(): + print(f" {line}", file=sys.stderr) + if e.stderr: + print() + print("Error Output:", file=sys.stderr) + for line in e.stderr.splitlines(): + print(f" {line}", file=sys.stderr) + print() print(traceback.format_exc(), file=sys.stderr) return e.returncode except Exception as e: diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index 7064de4..30c2381 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -821,12 +821,20 @@ def main(args): return 0 except subprocess.CalledProcessError as e: - print( - f"""Error: failed to invoke command. - \tCommand: {e.cmd} - \tReturn Code: {e.returncode}""", - file=sys.stderr, - ) + print("Error: failed to invoke command", file=sys.stderr) + print(f" Command: {e.cmd}", file=sys.stderr) + print(f" Return Code: {e.returncode}", file=sys.stderr) + if e.stdout: + print() + print("Output:", file=sys.stderr) + for line in e.stdout.splitlines(): + print(f" {line}", file=sys.stderr) + if e.stderr: + print() + print("Error Output:", file=sys.stderr) + for line in e.stderr.splitlines(): + print(f" {line}", file=sys.stderr) + print() print(traceback.format_exc(), file=sys.stderr) return e.returncode except Exception as e: From 40a8d3b17c47cb68bc848ce8fdec29092b5dbbed Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 15:56:46 -0700 Subject: [PATCH 09/12] Add intermediate certs and provisioning profile to build keychain --- apple-certs/DeveloperIDG2CA.cer | Bin 0 -> 1090 bytes apple-certs/README.md | 24 ++++++ ...artichoke-provisioning-profile-signing.cer | Bin 0 -> 1349 bytes macos_sign_and_notarize.py | 81 +++++++++++++----- 4 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 apple-certs/DeveloperIDG2CA.cer create mode 100644 apple-certs/README.md create mode 100644 apple-certs/artichoke-provisioning-profile-signing.cer diff --git a/apple-certs/DeveloperIDG2CA.cer b/apple-certs/DeveloperIDG2CA.cer new file mode 100644 index 0000000000000000000000000000000000000000..8cbcf6f46ce8dcd0fb6e55441867a4608c032860 GIT binary patch literal 1090 zcmXqLVzD!5Vpdzg%*4pVBvQYH!T#)Y&#KeSzLS=8RTLj;iFG#MW#iOp^Jx3d%gD&h z%3zRW$Zf#M#vIDRCd?EXY$$9X2;y)Fb2%0iSS&nCU+yalX;MxjO;0c zCPpP>Z!@woFgG#sGXTZ8n3@lXWlhc{<6@_ zAeVa$Zs~tCyBS(Lx(wQVee%4v9DmC6_sxzm_*Wacv6#I#)Z>*hPs%}3Q09g3DcH!m}M zzME}-$);}wYkbz|o;?}o+I{{v$FroX4pX<9$DEPod-(p&(x<&A1GYQN=TcU6b?Bdx zUj1OoE6d9jOHD2wI{m=7UT>Su;iqa>RnM*J)hhopPh{Hk2Vdj$KD}68xZHa2OrI*H z#lG(g1g~CeYi#?H7fwq1S7-2zs_8d`#)~n?ko^>O~S}P zBuwq0phvQHX3=yH`M&5=acZK!OOzXoLyRRCDz!!3*x{YIYWcZo(++VRH_f_`f37RD zIVso6^6S55-|LI6bjZK$IKcnor?K1?f$fK41kGK{SvD+jncI`WTU{$#cV_G457+W- zMXMT?mRx?=Uwf(Jh2ioUN9FH7YKmOf(3s#R_GaGBE{9dpS`QQ3xOxisZ+hvx@ma(w zc&)N$X|k%KGE@HK=&108*Ije(pZQ)tKmPXdw<*i>UG(#PZ7V9c!nRmnw>qJnx@hvN w+Yzt&uIxN=z~GOOl<%%dJSvtmnyePgy!ZUclRbQUTGvOdj=AvX_8Ec00Cez?EdT%j literal 0 HcmV?d00001 diff --git a/apple-certs/README.md b/apple-certs/README.md new file mode 100644 index 0000000..4b546f0 --- /dev/null +++ b/apple-certs/README.md @@ -0,0 +1,24 @@ +# Apple Certificates + +Several certificates are required by the build keychain used in the codesigning +process. + +## Certificate Chain + +The build keychain must include the all intermediate certificates for the +codesigning certificate. + +All of Apple's CAs can be found at: +. + +The Developer ID Application certificate used for codesigning has "Developer +ID - G2 (Expiring 09/17/2031 00:00:00 UTC)" as an intermediate in its +certificate chain. + +The root certificate in the chain is + +## Provisioning Profile + +`artichoke-provisioning-profile-signing.cer` contains a provisioning profile +which is associated with the Developer ID application and is required for +signing. diff --git a/apple-certs/artichoke-provisioning-profile-signing.cer b/apple-certs/artichoke-provisioning-profile-signing.cer new file mode 100644 index 0000000000000000000000000000000000000000..bb3d29edfbff39814c7dbbda039327bcf34c5dfb GIT binary patch literal 1349 zcmXqLVs$iVV$oc{%*4pV#8Da({i|y6)eQ!`Y@Awc9&O)w85y}*84Mby8FCwNvN4CU zun9AT1{(?+2!c3VJY0?i1v#k-o_Wc7hB^jXAVF>(Wr$#Seo;dWA_@e=M9<|m5{@Sk(GhDiIJZHD9**y#K_3-K-4C@>k(Iove-_` zqAmBMRm|n>+h@Gs=03Lm^Zkf93~QTz<*#TiY$%+zrP$|#kHU>*Z2dEYPW3InwL^{b z)eeCW1;wwn>PwP#EUuhr&|CAnZSvWFz7rQVUkW@Q-uG&!uf9!yn$FYdN2cgHJe-^M z=zHIpUGfj7#n?@Ad14?^b@cf?g{Qe06PDH-SZwj> z)pOZvHs(fdTsM>7uaW!|q%cWk*Hf#wLvfFmxkRnvbG7rl?Q&myx4%zC{#}M=<_npa z85tNCH!)rThR6j2do~VjHbz!fc1A`PV*^72eHh<>u}wLnq@=(~Uq3&&xIixvl#=w4 z^K-#m17qFt@{}S2O9NS8^vUwEh_Q(D%@P*nZ{$z6to*?^_w6EKrybq}20S2XVMfOP zEKCLr2J#@DGK++PSc6E1x;W>`Z4wFHZ(i^E<{o)8C}i<=pp#@kN)%X{7=?ixejw(C zruF0$Rz`!yzXpwmVNPUXGHBe!)3_O!YBLk_l2aA(^At)lGK&?G;mI*oA*oU!F|Sgg zAhDrmekRO4GI0=~Eih;5ziA5<2C8#WbPeTwY#|6wC-iQWN1BiVqYz{Gi}uVP;})FyMy=4huI66R;>iEg66*pOK+@ z@zklZtoakN#V$?qvf}Sv>gd3%`fbmlQ=9$6mi71#qxt&ICU{9j+5J>v&IKff;840B-_ou}A_#MgMc$^vGzw^^#%V~Sg-9EAI(yd+r;qYr8Yo6U?7d!A> N(job1y8a@`%K%1w?iT<6 literal 0 HcmV?d00001 diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index 30c2381..a4bf5f9 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -159,7 +159,7 @@ def codesigning_identity(): Codesigning identity and name of the Apple Developer ID Application. """ - return "230CC499559E4A43CFCCD853B938C17E69C5ECB6" + return "Developer ID Application: Ryan Lopopolo (VDKP67932G)" def notarization_apple_id(): @@ -233,6 +233,8 @@ def create_keychain(*, keychain_password): ) for line in proc.stdout.splitlines(): print(line) + print(f"Created keychain at {keychain_path()}") + # security set-keychain-settings -lut 900 "$keychain_path" proc = subprocess.run( ["security", "set-keychain-settings", "-lut", "900", str(keychain_path())], @@ -243,6 +245,8 @@ def create_keychain(*, keychain_password): ) for line in proc.stdout.splitlines(): print(line) + print("Set keychain to be ephemeral") + # security unlock-keychain -p "$keychain_password" "$keychain_path" proc = subprocess.run( [ @@ -259,6 +263,7 @@ def create_keychain(*, keychain_password): ) for line in proc.stdout.splitlines(): print(line) + print(f"Unlocked keychain at {keychain_path()}") def delete_keychain(): @@ -280,7 +285,6 @@ def delete_keychain(): ) for line in proc.stdout.splitlines(): print(line) - print() if proc.returncode == 0: print(f"Keychain deleted from {keychain_path()}") @@ -385,22 +389,60 @@ def import_codesigning_certificate(): for line in proc.stdout.splitlines(): print(line) - proc = subprocess.run( - [ - "security", - "find-identity", - "-p", - "codesigning", - "-v", - str(keychain_path()), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - ) - for line in proc.stdout.splitlines(): - print(line) + with log_group("Import intermediate certificates"): + proc = subprocess.run( + [ + "security", + "import", + "apple-certs/DeveloperIDG2CA.cer", + "-k", + str(keychain_path()), + "-T", + "/usr/bin/codesign", + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) + + with log_group("Import provisioning profile"): + proc = subprocess.run( + [ + "security", + "import", + "apple-certs/artichoke-provisioning-profile-signing.cer", + "-k", + str(keychain_path()), + "-T", + "/usr/bin/codesign", + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) + + with log_group("Show codesigning identities"): + proc = subprocess.run( + [ + "security", + "find-identity", + "-p", + "codesigning", + str(keychain_path()), + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) def setup_codesigning_and_notarization_keychain(*, keychain_password): @@ -843,8 +885,7 @@ def main(args): return 1 finally: # Purge keychain. - # delete_keychain() - pass + delete_keychain() if __name__ == "__main__": From 655e8df438a249d62b147c10bf86a7cbdb708578 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 17:11:48 -0700 Subject: [PATCH 10/12] Add build keychain to search path --- apple-certs/README.md | 2 - macos_sign_and_notarize.py | 127 ++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 59 deletions(-) diff --git a/apple-certs/README.md b/apple-certs/README.md index 4b546f0..5dcebc7 100644 --- a/apple-certs/README.md +++ b/apple-certs/README.md @@ -15,8 +15,6 @@ The Developer ID Application certificate used for codesigning has "Developer ID - G2 (Expiring 09/17/2031 00:00:00 UTC)" as an intermediate in its certificate chain. -The root certificate in the chain is - ## Provisioning Profile `artichoke-provisioning-profile-signing.cer` contains a provisioning profile diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index a4bf5f9..3e9a794 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -265,6 +265,38 @@ def create_keychain(*, keychain_password): print(line) print(f"Unlocked keychain at {keychain_path()}") + # Per `man codesign`, the keychain filename passed via the `--keychain` + # argument will not be searched to resolve the signing identity's + # certificate chain unless it is also on the user's keychain search list. + # + # `security create-keychain` does not add keychains to the search path. + # _Opening_ them does, as well as explicitly manipulating the search path + # with `security list-keychains -s`. + # + # This stackoverflow post explains the solution: + # + # + # `security delete-keychain` removes the keychain from the search path. + proc = subprocess.run( + ["security", "list-keychains", "-d", "user"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + search_path = [line.strip().strip('"') for line in proc.stdout.splitlines()] + search_path.append(str(keychain_path())) + proc = subprocess.run( + ["security", "list-keychains", "-d", "user", "-s"] + search_path, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) + print(f"Set keychain search path: {', '.join(search_path)}") + def delete_keychain(): """ @@ -333,6 +365,37 @@ def import_notarization_credentials(): print(line) +def import_certificate(*, path, name=None, password=None): + """ + Import a certificate at a given path into the build keychain. + """ + + # security import certificate.p12 \ + # -k "$keychain_path" \ + # -P "$MACOS_CERTIFICATE_PWD" \ + # -T /usr/bin/codesign + command = [ + "security", + "import", + str(path), + "-k", + str(keychain_path()), + "-T", + "/usr/bin/codesign", + ] + if password is not None: + command.extend(["-P", password]) + + proc = subprocess.run( + command, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True + ) + for line in proc.stdout.splitlines(): + print(line) + + cert_name = path if name is None else name + print(f"Imported certificate {cert_name}") + + def import_codesigning_certificate(): """ Import codesigning certificate into the codesigning and notarization process @@ -365,67 +428,17 @@ def import_codesigning_certificate(): with tempfile.TemporaryDirectory() as tempdirname: cert = Path(tempdirname).joinpath("certificate.p12") cert.write_bytes(certificate) - # security import certificate.p12 \ - # -k "$keychain_path" \ - # -P "$MACOS_CERTIFICATE_PWD" \ - # -T /usr/bin/codesign - proc = subprocess.run( - [ - "security", - "import", - str(cert), - "-k", - str(keychain_path()), - "-P", - certificate_password, - "-T", - "/usr/bin/codesign", - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + import_certificate( + path=cert, name="Developer Application", password=certificate_password ) - for line in proc.stdout.splitlines(): - print(line) - - with log_group("Import intermediate certificates"): - proc = subprocess.run( - [ - "security", - "import", - "apple-certs/DeveloperIDG2CA.cer", - "-k", - str(keychain_path()), - "-T", - "/usr/bin/codesign", - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - ) - for line in proc.stdout.splitlines(): - print(line) with log_group("Import provisioning profile"): - proc = subprocess.run( - [ - "security", - "import", - "apple-certs/artichoke-provisioning-profile-signing.cer", - "-k", - str(keychain_path()), - "-T", - "/usr/bin/codesign", - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + import_certificate( + path="apple-certs/artichoke-provisioning-profile-signing.cer" ) - for line in proc.stdout.splitlines(): - print(line) + + with log_group("Import certificate chain"): + import_certificate(path="apple-certs/DeveloperIDG2CA.cer") with log_group("Show codesigning identities"): proc = subprocess.run( From 87c578d82392e1eb5f27d8d5cd497a06b723951c Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 17:36:15 -0700 Subject: [PATCH 11/12] Factor out merging stdout and stderr into a function --- macos_sign_and_notarize.py | 191 ++++++++++++------------------------- 1 file changed, 62 insertions(+), 129 deletions(-) diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index 3e9a794..a1be233 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -15,6 +15,28 @@ from pathlib import Path +def run_command_with_merged_output(command): + """ + Run the given command as a subprocess and merge its stdout and stderr + streams. + + This is useful for funnelling all output of a command into a GitHub Actions + log group. + + This command uses `check=True` when delegating to `subprocess`. + """ + + proc = subprocess.run( + command, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + for line in proc.stdout.splitlines(): + print(line) + + def set_output(*, name, value): """ Set an output for a GitHub Actions job. @@ -55,15 +77,14 @@ def attach_disk_image(image, *, readwrite=False): ] else: command = ["/usr/bin/hdiutil", "attach", str(image)] + run_command_with_merged_output(command) - subprocess.run(command, check=True) mounted_image = disk_image_mount_path() yield mounted_image finally: with log_group("Detatching disk image"): - subprocess.run( + run_command_with_merged_output( ["/usr/bin/hdiutil", "detach", str(mounted_image)], - check=True, ) @@ -218,51 +239,33 @@ def create_keychain(*, keychain_password): with log_group("Setup notarization keychain"): # security create-keychain -p "$keychain_password" "$keychain_path" - proc = subprocess.run( + run_command_with_merged_output( [ "security", "create-keychain", "-p", keychain_password, str(keychain_path()), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + ] ) - for line in proc.stdout.splitlines(): - print(line) print(f"Created keychain at {keychain_path()}") # security set-keychain-settings -lut 900 "$keychain_path" - proc = subprocess.run( - ["security", "set-keychain-settings", "-lut", "900", str(keychain_path())], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + run_command_with_merged_output( + ["security", "set-keychain-settings", "-lut", "900", str(keychain_path())] ) - for line in proc.stdout.splitlines(): - print(line) print("Set keychain to be ephemeral") # security unlock-keychain -p "$keychain_password" "$keychain_path" - proc = subprocess.run( + run_command_with_merged_output( [ "security", "unlock-keychain", "-p", keychain_password, str(keychain_path()), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + ] ) - for line in proc.stdout.splitlines(): - print(line) print(f"Unlocked keychain at {keychain_path()}") # Per `man codesign`, the keychain filename passed via the `--keychain` @@ -286,15 +289,9 @@ def create_keychain(*, keychain_password): ) search_path = [line.strip().strip('"') for line in proc.stdout.splitlines()] search_path.append(str(keychain_path())) - proc = subprocess.run( - ["security", "list-keychains", "-d", "user", "-s"] + search_path, - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + run_command_with_merged_output( + ["security", "list-keychains", "-d", "user", "-s"] + search_path ) - for line in proc.stdout.splitlines(): - print(line) print(f"Set keychain search path: {', '.join(search_path)}") @@ -341,7 +338,7 @@ def import_notarization_credentials(): # --password "$MACOS_NOTARIZE_APP_PASSWORD" \ # --team-id "VDKP67932G" \ # --keychain "$keychain_path" - proc = subprocess.run( + run_command_with_merged_output( [ "/usr/bin/xcrun", "notarytool", @@ -356,13 +353,7 @@ def import_notarization_credentials(): "--keychain", str(keychain_path()), ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, ) - for line in proc.stdout.splitlines(): - print(line) def import_certificate(*, path, name=None, password=None): @@ -386,11 +377,7 @@ def import_certificate(*, path, name=None, password=None): if password is not None: command.extend(["-P", password]) - proc = subprocess.run( - command, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True - ) - for line in proc.stdout.splitlines(): - print(line) + run_command_with_merged_output(command) cert_name = path if name is None else name print(f"Imported certificate {cert_name}") @@ -441,21 +428,9 @@ def import_codesigning_certificate(): import_certificate(path="apple-certs/DeveloperIDG2CA.cer") with log_group("Show codesigning identities"): - proc = subprocess.run( - [ - "security", - "find-identity", - "-p", - "codesigning", - str(keychain_path()), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + run_command_with_merged_output( + ["security", "find-identity", "-p", "codesigning", str(keychain_path())] ) - for line in proc.stdout.splitlines(): - print(line) def setup_codesigning_and_notarization_keychain(*, keychain_password): @@ -475,7 +450,7 @@ def setup_codesigning_and_notarization_keychain(*, keychain_password): # security set-key-partition-list \ # -S "apple-tool:,apple:,codesign:" \ # -s -k "$keychain_password" "$keychain_path" - proc = subprocess.run( + run_command_with_merged_output( [ "security", "set-key-partition-list", @@ -485,14 +460,8 @@ def setup_codesigning_and_notarization_keychain(*, keychain_password): "-k", keychain_password, str(keychain_path()), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + ] ) - for line in proc.stdout.splitlines(): - print(line) def codesign_binary(*, binary_path): @@ -510,7 +479,7 @@ def codesign_binary(*, binary_path): # --force \ # "$binary_path" with log_group(f"Run codesigning [{binary_path.name}]"): - proc = subprocess.run( + run_command_with_merged_output( [ "/usr/bin/codesign", "--keychain", @@ -526,14 +495,8 @@ def codesign_binary(*, binary_path): "-vvv", "--force", str(binary_path), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, + ] ) - for line in proc.stdout.splitlines(): - print(line) def create_notarization_bundle(*, release_name, binaries, resources): @@ -578,7 +541,7 @@ def create_notarization_bundle(*, release_name, binaries, resources): # -volname "Artichoke Ruby nightly" \ # -srcfolder "$release_name" \ # -ov -format UDRW name.dmg - subprocess.run( + run_command_with_merged_output( [ "/usr/bin/hdiutil", "create", @@ -592,8 +555,7 @@ def create_notarization_bundle(*, release_name, binaries, resources): "UDRW", "-verbose", str(dmg_writable), - ], - check=True, + ] ) with log_group("Set disk image icon"): @@ -605,40 +567,28 @@ def create_notarization_bundle(*, release_name, binaries, resources): "https://artichoke.github.io/logo/Artichoke-dmg.icns", str(icns) ) shutil.copy(icns, dmg_icns_path) - subprocess.run( - [ - "/usr/bin/SetFile", - "-c", - "icnC", - str(dmg_icns_path), - ], - check=True, + run_command_with_merged_output( + ["/usr/bin/SetFile", "-c", "icnC", str(dmg_icns_path)] ) + # Tell the volume that it has a special file attribute - subprocess.run( - [ - "/usr/bin/SetFile", - "-a", - "C", - str(mounted_image), - ], - check=True, + run_command_with_merged_output( + ["/usr/bin/SetFile", "-a", "C", str(mounted_image)] ) with log_group("Shrink disk image to fit"): - subprocess.run( + run_command_with_merged_output( [ "/usr/bin/hdiutil", "resize", "-size", f"{get_image_size(dmg_writable)}m", str(dmg_writable), - ], - check=True, + ] ) with log_group("Compress disk image"): - subprocess.run( + run_command_with_merged_output( [ "/usr/bin/hdiutil", "convert", @@ -649,9 +599,9 @@ def create_notarization_bundle(*, release_name, binaries, resources): "zlib-level=9", "-o", str(dmg), - ], - check=True, + ] ) + dmg_writable.unlink() codesign_binary(binary_path=dmg) @@ -733,15 +683,8 @@ def staple_bundle(*, bundle): """ with log_group("Staple disk image"): - subprocess.run( - [ - "/usr/bin/xcrun", - "stapler", - "staple", - "-v", - str(bundle), - ], - check=True, + run_command_with_merged_output( + ["/usr/bin/xcrun", "stapler", "staple", "-v", str(bundle)] ) @@ -751,15 +694,8 @@ def validate(*, bundle, binary_names): """ with log_group("Verify disk image staple"): - subprocess.run( - [ - "/usr/bin/xcrun", - "stapler", - "validate", - "-v", - str(bundle), - ], - check=True, + run_command_with_merged_output( + ["/usr/bin/xcrun", "stapler", "validate", "-v", str(bundle)] ) with log_group("Verify disk image signature"): @@ -767,7 +703,7 @@ def validate(*, bundle, binary_names): # --context context:primary-signature \ # 2022-09-03-test-codesign-notarize-dmg-v1.dmg \ # -v - subprocess.run( + run_command_with_merged_output( [ "/usr/sbin/spctl", "-a", @@ -777,15 +713,14 @@ def validate(*, bundle, binary_names): "context:primary-signature", str(bundle), "-v", - ], - check=True, + ] ) with attach_disk_image(bundle) as mounted_image: for binary in binary_names: mounted_binary = mounted_image.joinpath(binary) with log_group(f"Verify signature: {binary}"): - subprocess.run( + run_command_with_merged_output( [ "/usr/bin/codesign", "--verify", @@ -794,20 +729,18 @@ def validate(*, bundle, binary_names): "--strict=all", "-vvv", str(mounted_binary), - ], - check=True, + ] ) with log_group(f"Display signature: {binary}"): - subprocess.run( + run_command_with_merged_output( [ "/usr/bin/codesign", "--display", "--check-notarization", "-vvv", str(mounted_binary), - ], - check=True, + ] ) From 1c697394aca37e81954435d3517aec7f0ea994ed Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 4 Sep 2022 17:36:34 -0700 Subject: [PATCH 12/12] Fix du handling on old macOS --- macos_sign_and_notarize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macos_sign_and_notarize.py b/macos_sign_and_notarize.py index a1be233..2606b19 100755 --- a/macos_sign_and_notarize.py +++ b/macos_sign_and_notarize.py @@ -119,7 +119,7 @@ def get_image_size(image): capture_output=True, text=True, ) - size = int(proc.stdout.split(" ")[0]) + size = int(proc.stdout.split()[0]) return (size * 512 / 1000 / 1000) + 1