Repository: bitcoin/bitcoin (language: cpp)

Issue:
macOS App Notarization & Stapling
> The Apple notary service is an automated system that scans your software for malicious content, checks for code-signing issues, and returns the results to you quickly. If there are no issues, the notary service generates a ticket for you to staple to your software; the notary service also publishes that ticket online where Gatekeeper can find it.

> Beginning in **macOS 10.14.5**, all new or updated kernel extensions and all software from developers new to distributing with Developer ID must be notarized in order to run. In a future version of macOS, notarization will be required by default for all software.

The currently available version of macOS is 10.14.4.

Apple's notary service requires you to adopt the following protections:
* Enable code-signing for all of the executables you distribute.
* Enable the Hardened Runtime capability for your executable targets, as described in Enable hardened runtime.
* Use a `Developer ID` application, kernel extension, or installer certificate for your code-signing signature.
* Include a secure timestamp with your code-signing signature. (The Xcode distribution workflow includes a secure timestamp by default. For custom workflows, include the `--timestamp` option when running the codesign tool.)
* Don’t include the `com.apple.security.get-task-allow` entitlement with the value set to any variation of true.
* Link against the macOS 10.9 or later SDK.

More info available here: https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution


Discussion / hints:
@jonasschnelli @theuni 
If I'm reading this right, it only applies to "software from developers *new to distributing* with Developer ID", i.e. since you already shipped signed Mac binaries, you should be exempt for the time being. Or were the binaries signed without a Developer ID somehow?
Moving this to `v0.20.0` as we shouldn't have to solve anything here for `v0.19.0`.
I see this message with v0.19 RC3 on MacOS Catalina.

<img width="403" alt="Screen Shot 2019-10-30 at 10 15 40 AM" src="https://user-images.githubusercontent.com/326147/67903298-6992c200-fb28-11e9-9159-a9956662218a.png">

Same issue (macOS 10.15 Catalina).

Note that when copying the DMG from my Gitian machine it doesn't show the same warning. Only when I download it. Files are identical, but presumably macOS tracks this stuff somewhere.
I'm on 10.15 too. I wonder what the difference is?

On Thu, Oct 31, 2019 at 4:18 AM Sjors Provoost <notifications@github.com>
wrote:

> On 10.15 I don't get this message when I update my existing installation
> from the signed DMG, nor when I delete it.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/bitcoin/bitcoin/issues/15774?email_source=notifications&email_token=AACPUAYZQAGRSGET36JZK3DQRK5JNA5CNFSM4HEPXQW2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECXLWDQ#issuecomment-548322062>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACPUA6SMQ3O5E4I237DB63QRK5JNANCNFSM4HEPXQWQ>
> .
>

@moneyball none. I updated my comment to reflect that it matters if you get the file as a download or via SSH from a Gitian machine.
> If I'm reading this right, it only applies to "software from developers _new to distributing_ with Developer ID", i.e. since you already shipped signed Mac binaries, you should be exempt for the time being. Or were the binaries signed without a Developer ID somehow?

This is how I too initially understood the documentation. However, as others have pointed out, this does not seem to be the case. With my own project that has distributed code signed binaries for a while now, those same code signed binaries, when downloaded, are actually blocked from running on Catalina with the message shown in @moneyball 's screenshot.

-------------------------------------

Notarization can be done either _after-the-fact_ (post-release) for existing code signed binaries, or during the release process' final steps using `altool` (see https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/customizing_the_notarization_workflow for command examples starting at the "Upload Your App to the Notarization Service" section). This would need to be performed by the code signer.

If all goes well, the binary package will then be recognized by GateKeeper, providing that an active internet connection is available. It is possible to "staple" the notarization receipt to the binary package so that an internet connection is not required, but that would change it's sha256sum, which would no longer match the hashes from gitian's output.

Note: There is an intermittent bug in Apple's notarization system that may block the binary from being notarized properly. Details can be found on Apple's Developer Forums: https://forums.developer.apple.com/thread/118465
> If all goes well, the binary package will then be recognized by GateKeeper, providing that an active internet connection is available.

Which implies giving Apple an overview of all Bitcoin Core macOS user, the exact version they installed and when. Unless GateKeeper downloads a list of hashes, rather than uploading the hash. 

> It is possible to "staple" the notarization receipt to the binary package so that an internet connection is not required, but that would change it's sha256sum, which would no longer match the hashes from gitian's output.

This seems worth some additional hassle for the privacy benefit. During a recent IRC meeting the idea came up to make a tool that strips the extra information from the package and then checks the resulting checksum. 
I had to make sure my app was notarized and "stapled". For those interested, here is what I needed to do to automate the task on travis:

https://github.com/btcpayserver/BTCPayServer.Vault/blob/master/Build/travis/applesign.sh

Basically, I needed to:
* extract the unsigned dmg,
* sign the app,
* rebundle the app as a dmg,
* sign the dmg,
* notarize the dmg,
* wait that it succeed
* Apply the staple to the dmg

Note that applying the staple to the dmg is optional. However, if you don't apply it, gatekeeper will need internet connection to verify the dmg.

I think that I should also **staple the .App folder** itself and not only the dmg if I wanted gatekeeper to be 100% happy when offline. (yes, you can staple the .App folder... I have no idea why it works, where the staple get saved?)
The documentation is very bad, so I don't really know...

If you want to also staple the `.App` I think you need a two steps notarization: First the `.App` folder as a tar file, then the dmg after it has been built.

Take a good note about the parameters I passed to `codesign`, this took a while to get right.
Hi there,
I have just finished signing a **dotnet core application** for distributing app outside the Mac App Store. There are several recommendations by apple and as @NicolasDorier mentioned the documentation to do it outside XCode is very bad quality. 

Take a look at my code: https://github.com/molnard/WalletWasabi/blob/4f499aab964d7c9298a0f041633fb20927d3a18f/WalletWasabi.Packager/MacSignTools.cs

The whole PR: https://github.com/zkSNACKs/WalletWasabi/pull/3041

Oh, just one more thing - I had to move our packager from linux to mac because there is no way to make it work otherwise. 
I looked a bit into this issue and I think a possible approach could be:

* During the manual macOS code signing process via `detached-sig-create.sh` (currently done by me), we could also notarize the .app bundle (and/or the `bitcoin-qt` binary).
* We "staple" the tickets from apple notarisation service directly onto the .app bundle (kinda pack the signature for the notarization into the application bundle so no network connection is required)
* We create the dmg with gitian (pulling in the detached signature & notarization). *This is when the gitian people build the win/osx signed builds.*
* We manually notarize the dmg file (2nd manual step, executed by the macOS code signing key holder)

*(requires testing in order to make sure this process works)*

Downsides:
* A 2nd manual step through macOS code signing maintainer

Alternatives:
* Create directly the dmg in 1st manual code signing step (kinda defeats the purpose of gitian,... so a bad alternative)

Risks:
* Since the final .dmg notarization is a manual one man show step, the question pops up if this could be exploited. I guess it can't. It only tells Apples Gatekeeper server that the gitian built .dmg file was notarized with someone holding the macOS code signing keys. Manually notarizing a malicious .dmg by a maintainer would still require to distribute that malicious .dmg somehow (also codesign).

Anyways, our binary distribution security is based on a completely different layer (deterministic builds with multiple signatures). The os protection with code-signing and notarization is nice but provides much less trust/security IMO.

Ping @theuni @dongcarl.
In practice, given that the macOS code signing maintainer is also a Gitian builder, they can do both manual steps in a single session? 
For test purposes, I notarized the 0.19.1rc**1** dmg file (RC1! not 2!).
If anyone can test if the 0.19.1rc1 gives them a notarization warning ("...apple can not check the app for malicious code"-error should be gone).
I just downloaded it (note: it won't check the notary stuff if you obtain it via SSH or build yourself) and didn't get a notarization warning. Just the usual "you downloaded from bitcoincore.org, are you sure".
Great. So just notarizing the .dmg during the release process would work.
Additionally, we could staple the notarization ticket onto the app bundle. But that would be non trivial via our gitian build process.
"Stapling" would allow detecting the notarization even if no internet connection is available.

I propose that we start with the "we just notarize the dmg"-approach and see if there is demand/need for the "stapling"-approach.
How do users verify the end DMG is generated deterministically by gitian?
@luke-jr it’s still the same process (detached signature). Just an additional flag.
The actual notarization binds to the hash of the deterministic built dmg.
So the notarization is a separate file? How does the end user get that?
@luke-jr 
From my understanding it works like this (simplified):
* We build the dmg in the same was as now
* The macOS codesign key holder notarizes the .dmg file:
* * During notarization, it uploads the .dmg file to apples server and checks its content
* * If the application has all required flags set, etc., it will approve the notarization and stores its hash, etc. on the Apple servers (notarization ticket)
* Once a user starts an application the first time, it checks it's installers hash (the .dmg file) against Apples notarization server and if the hash has an approved notarization ticket, the app launches.

So it needs online connectivity the first time the app launches. Though user can disable the notarization check in the macOS security settings.

It's mandatory since 10.14.5 (means the default security setting only allows notarized apps to launch).

There is an option to add the notarization ticket directly to the app to allow offline users to check the notarization (Apple calls that stapling). But it would complicated our build process. Can be done later.
> * Once a user starts an application the first time, it checks it's installers hash (the .dmg file) against Apples notarization server and if the hash has an approved notarization ticket, the app launches.

This part has me worried. Does Apple use a (provable) anonimzation technique here? Or is this giving them a list of IP address of Bitcoin Core users? Stapling the notarisation onto the DMG seems better, though it makes the process more complicated.
@Sjors: this is indeed a good point. Though I don't know if we can even prevent the ugly online check when we staple the notarization ticket to the application. Maybe it only checks it when the user is offline. But I agree, if we decide to notarize (and I guess we have to), stapling the ticket to the app is the preferred approach.

I just checked what stapling actually does.
It adds a file `CodeResources` to the application bundle (all other files are untouched).
We could add that `CodeResources` file to the detach signature git and "apply" it during gitian-osx-signer process.

The process would look like:
* the macOS code signer needs to run the .dmg creation step (a manual gitian-osx-signer.yml) after he ran the `detached-sig-create.sh`
* ... then, he needs to notarize the created .dmg file
* ... staple the notarization ticket to the .app folder (creates the `CodeResources` file)
* ... commit the detached signatures & the `CodeResources` file to https://github.com/bitcoin-core/bitcoin-detached-sigs
* ... all gitian builders pull the signature & the `CodeResource` file and apply it to the .app folder and create the dmg

Downside:
* More work for the macOS code signer (acceptable IMO)
* Notarization happens before we have proof of determinism and the possibility to check the hashes of other gitian builds (we already have that problem with code signing)
* No current reliable proof if this would not break determinism (I think it wouldn't)
This sounds like a reasonable approach to me.
FWIW, https://sigpipe.macromates.com/2020/macos-catalina-slow-by-design/ "Spawning a new Process" subheader.
At this point this isn't going to happen for 23.0. Moving this to 24.0
It seems that Xcode 13 ships with a new tool called `notarytool` making the whole process easier: https://scriptingosx.com/2021/07/notarize-a-command-line-tool-with-notarytool/

Also the process is supposed to be 10x faster than the old method via `altool` - see https://github.com/electron/electron-notarize/pull/82
might be relevant:
* https://gregoryszorc.com/blog/2022/08/08/achieving-a-completely-open-source-implementation-of-apple-code-signing-and-notarization/
* https://github.com/indygreg/PyOxidizer/tree/main/apple-codesign
* https://gregoryszorc.com/docs/apple-codesign/stable/
* https://crates.io/crates/apple-codesign
But it still phones home and tells Apple you installed Bitcoin Core, right?
Moving this to future. We can revisit depending on what happens during testing of release candidates for 24.x.
> I see this message with v0.19 RC3 on MacOS Catalina.
> 
> <img alt="Screen Shot 2019-10-30 at 10 15 40 AM" width="403" src="https://user-images.githubusercontent.com/326147/67903298-6992c200-fb28-11e9-9159-a9956662218a.png">

I am getting this error message when opening the rc1 24.x
I'm not sure if the release candidates get notarized (and when)?
> I'm not sure if the release candidates get notarized (and when)?

Notarization is not occuring at any point, for any binaries.
Ok, in that case the error message is as expected. You have to right click the first time and then hold `option` when you click `Open`. Needless to say this is an annoying anti open source move by Apple...
> Ok, in that case the error message is as expected. You have to right click the first time and then hold `option` when you click `Open`. Needless to say this is an annoying anti open source move by Apple...

This does not work when a macbook is mdm'ed (corporate and student macbooks). Can we get the rc1 dmg notarized and skip it on the release binaries?
Guys - the user experience is horrible. I download Bitcoin Core from the official webpage - Norton kills my download as it falsely recognizes it as a virus, if I temporarily disable Norton Apple prevents me from opening the downloaded program cause the signing key is outdated and BTC Core is not enabled for multi-key-signing and still depending on the outdated key of the departed Wladimir van der Laan, if I manage to get a valid signing key  and work around Apple preventions Norton kills the running software. This is the future of money? So what is the right way to run BTC Core?
The policy reads to me that notarization will **always** phone home, even in the case of having an externally-stapled ticket, _unless_ a connection is not available in which case the stapled ticket is locally verified. Therefore it seems to me that we have two options available:

1) Notarize the binary and somehow communicate to users that if they don't want Apple to know that they are running Bitcoin Core they must start it up with no internet connection (under the assumption that Gatekeeper only verifies first run).

2) Do not notarize the binary and include instructions similar to those seen on [this page](https://support.apple.com/en-us/HT202491#:~:text=These%20security%20checks%20have%20never%20included%20the%20user%E2%80%99s%20Apple%20ID%20or%20the%20identity%20of%20their%20device.%20To%20further%20protect%20privacy%2C%20we%20have%20stopped%20logging%20IP%20addresses%20associated%20with%20Developer%20ID%20certificate%20checks%2C%20and%20we%20will%20ensure%20that%20any%20collected%20IP%20addresses%20are%20removed%20from%20logs) (copied below) in the MacOS DMG/zip archive, which imply that it is (permanently) saved as an exception.

Personally I'd tend towards the latter option.

> This does not work when a macbook is mdm'ed (corporate and student macbooks). Can we get the rc1 dmg notarized and skip it on the release binaries?

I don't know if option 2 works on an "mdm'ed" macbook though...

----

## If you want to open an app that hasn’t been notarized or is from an unidentified developer

Running software that hasn’t been signed and notarized may expose your computer and personal information to malware that can harm your Mac or compromise your privacy. If you’re certain that an app you want to install is from a trustworthy source and hasn’t been tampered with, you can temporarily override your Mac security settings to open it.

If you still want to open an app for which the developer cannot be verified, open System Preferences.*

<img src="https://user-images.githubusercontent.com/6606587/233847231-ce1eda24-4d06-4687-9a81-da6f326a172b.png" width="250">

Go to Security & Privacy. Click the Open Anyway button in the General pane to confirm your intent to open or install the app. 

<img src="https://user-images.githubusercontent.com/6606587/233847252-02b46f58-f66a-48a6-90ae-415a07210dc1.png" width="450">

The warning prompt reappears, and if you're absolutely sure you want to open the app anyway, you can click Open. 

<img src="https://user-images.githubusercontent.com/6606587/233847256-fae72577-df40-4144-81ed-cdddf0d2183b.png" width="250">

The app is now saved as an exception to your security settings, and you can open it in the future by double-clicking it, just as you can any authorized app. 
I'm a bit mystified that this issue is over 4 years old. Near as I can tell, the release manager just needs to pas `--hardened-runtime` to the signing command, and the resulting binaries will be notorizable. I've personally tested this on v26.
> I'm a bit mystified that this issue is over 4 years old. Near as I can tell, the release manager just needs to pas `--hardened-runtime` to the signing command, and the resulting binaries will be notorizable. I've personally tested this on v26.

I don't think it's technically difficult, although it will probably need additional tooling and procedural changes to work in the Guix build system.

I believe the current main objection is a philosophical one to the fact that notarizing means that installing the application will result in Macs phoning home to Apple, regardless of how the notarization is applied. This essentially hands a list of everyone who has installed Bitcoin Core on a Mac over to Apple, which some people are uncomfortable with us doing that.
Not if the notarization is stapled to the binary, no. That should prevent phoning home. I have not used Wireshark to verify this, but that's what the documentation claims.
@maaku
My tests in 2020 (https://github.com/bitcoin/bitcoin/pull/18187#issuecomment-592453829) showed me, that regardless of stapling, macOS does an onlinecheck of the notarization (if the user has internet connection).

Not sure if that has changed. Might be worth to redo https://github.com/bitcoin/bitcoin/pull/18187#issuecomment-592453829.

That unnecessary "calling-apple" was the reason to not do notarization back in 2020.
@jonasschnelli Sounds like bug 57278824, which was fixed back in 2020 just a few months after you ran your test:

> There’s one further wrinkle here: Once Brigitte removed the com.apple.security.cs.disable-library-validation entitlement from their app the problem didn’t go away! And that’s the result of a historical bug in Gatekeeper (r. 57278824). Prior to 10.15.4 Gatekeeper does not recognise the implied library validation that you get when you enabled the hardened runtime. So, even though you have library validation enabled via the hardened runtime, Gatekeeper still runs these extra checks )-:

https://developer.apple.com/forums/thread/695026

Also, I can't verify what steps you used to test the notary because your link to your own website is down, and not captured by archive.org.
> Not if the notarization is stapled to the binary, no. That should prevent phoning home. I have not used Wireshark to verify this, but that's what the documentation claims.

Can you link to this documentation you're talking about? Or is that the developer thread above?

Reading https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow#3087720, it's not clear that no phone home happens if there is a ticket present, just that, if there is one, it can be used as a fallback when there is no network connection, but I assume Gatekeeper will always attempt an online check first:
> Notarization produces a ticket that tells Gatekeeper that your app is notarized. After notarization completes, the next time any user attempts to run your app on macOS 10.14 or later, Gatekeeper finds the ticket online. This includes users who downloaded your app before notarization.

> You should also attach the ticket to your software using the stapler tool, so that future distributions include the ticket. This ensures that Gatekeeper can find the ticket even when a network connection isn’t available. 
Might be relevant:

- https://blog.jacopo.io/en/post/apple-ocsp/
- https://lapcatsoftware.com/articles/ocsp.html
Does the OS not phone home if the application is not notarized?
If so, how can it distinguish notarized+not_stapled vs not_notarized applications?
> If so, how can it distinguish notarized+not_stapled vs not_notarized applications?

You've hit the nail on the head: notarized+not_stapled !notarized apps are bit-for-bit identical. It phones home for all applications being run, except those with stapled notary (maybe, this is disputed above).
Did some tests on MacOS 13.5.1. There is no difference in phone home behavior for no notarization at all, notarization without stapling, and stapled notarization. Tested with Bitcoin Core 26.0, Wireshark (notarized but not stapled), and Firefox (notarized and stapled), and did packet capture for each with Wireshark. They all resulted in communication with api.apple-cloudkit.com, which, AFAIK, is the domain for the notarization check.

To replicate this experiment, the command `spctl -a -vvv -t install <path to app>` can be used to do a notarization check that will always phone home, and `stapler validate -v <path to app>` can be used to check whether an app has a stapled notarization (note that this command also phones home, I believe to check that the stapled notarization matches).

Given that MacOS is just phoning home all the time, I think we should move ahead with notarizing our MacOS releases to improve the installation experience. Although stapling sounds like a good idea, at this point, I sincerely doubt that Apple will change their tooling to stop phoning home for stapled apps, but maybe we should do so in the hopes that they change their minds.
> Given that MacOS is just phoning home all the time, I think we should move ahead with notarizing our MacOS releases to improve the installation experience. Although stapling sounds like a good idea, at this point, I sincerely doubt that Apple will change their tooling to stop phoning home for stapled apps, but maybe we should do so in the hopes that they change their minds.

In this case notarizing MacOS releases seems the most reasonable course of action to me too. Of course we'd like the additional privacy of not phoning home, if it were feasible. But in the case it's unavoidable then improving the installation experience for (all) MacOS users makes sense.

I am curious though, what happens if the user does this:

1. runs `sudo spctl --master-disable`
2. installs bitcoin core
3. runs bitcoin core
4. runs `sudo spctl --master-enable`

Does the app phone home during that sequence? If not, does it phone home the _next_ time Bitcoin Core is started (after Gatekeeper was re-enabled)?
Right-clicking to bypass Gatekeeper will likely be unavailable in the next version of macOS (Sequoia / 15).
https://developer.apple.com/news/?id=saqachfa

Users can still go to System Settings > Privacy & Security to "review security information" and launch the application regardless of missing notarization.

This might be a good time to reconsider notarization for the project.
macOS Sequoia is out, and both right-click gatekeeper override and the master spctl disable are removed.

Since it has been shown that macOS phones home regardless of signed binary status, maybe it is time to revisit this? Right now the user experience is terrible, and for literally no benefit.

Fix patch:
diff --git a/contrib/guix/guix-codesign b/contrib/guix/guix-codesign
index 4694209e00d5b..dedee135b4ab1 100755
--- a/contrib/guix/guix-codesign
+++ b/contrib/guix/guix-codesign
@@ -137,7 +137,7 @@ fi
 
 
 ################
-# Unsigned tarballs SHOULD exist
+# Codesigning tarballs SHOULD exist
 ################
 
 # Usage: outdir_for_host HOST SUFFIX
@@ -149,13 +149,13 @@ outdir_for_host() {
 }
 
 
-unsigned_tarball_for_host() {
+codesigning_tarball_for_host() {
     case "$1" in
         *mingw*)
-            echo "$(outdir_for_host "$1")/${DISTNAME}-win64-unsigned.tar.gz"
+            echo "$(outdir_for_host "$1")/${DISTNAME}-win64-codesigning.tar.gz"
             ;;
         *darwin*)
-            echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-unsigned.tar.gz"
+            echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-codesigning.tar.gz"
             ;;
         *)
             exit 1
@@ -164,22 +164,22 @@ unsigned_tarball_for_host() {
 }
 
 # Accumulate a list of build directories that already exist...
-hosts_unsigned_tarball_missing=""
+hosts_codesigning_tarball_missing=""
 for host in $HOSTS; do
-    if [ ! -e "$(unsigned_tarball_for_host "$host")" ]; then
-        hosts_unsigned_tarball_missing+=" ${host}"
+    if [ ! -e "$(codesigning_tarball_for_host "$host")" ]; then
+        hosts_codesigning_tarball_missing+=" ${host}"
     fi
 done
 
-if [ -n "$hosts_unsigned_tarball_missing" ]; then
+if [ -n "$hosts_codesigning_tarball_missing" ]; then
     # ...so that we can print them out nicely in an error message
     cat << EOF
-ERR: Unsigned tarballs do not exist
+ERR: Codesigning tarballs do not exist
 ...
 
 EOF
-for host in $hosts_unsigned_tarball_missing; do
-    echo "     ${host} '$(unsigned_tarball_for_host "$host")'"
+for host in $hosts_codesigning_tarball_missing; do
+    echo "     ${host} '$(codesigning_tarball_for_host "$host")'"
 done
 exit 1
 fi
@@ -371,7 +371,7 @@ EOF
                                         OUTDIR="$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST" codesigned)" \
                                         DIST_ARCHIVE_BASE=/outdir-base/dist-archive \
                                         DETACHED_SIGS_REPO=/detached-sigs \
-                                        UNSIGNED_TARBALL="$(OUTDIR_BASE=/outdir-base && unsigned_tarball_for_host "$HOST")" \
+                                        CODESIGNING_TARBALL="$(OUTDIR_BASE=/outdir-base && codesigning_tarball_for_host "$HOST")" \
                                       bash -c "cd /bitcoin && bash contrib/guix/libexec/codesign.sh"
     )
 
diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh
index 6c252e787022a..e955cf92de688 100755
--- a/contrib/guix/libexec/build.sh
+++ b/contrib/guix/libexec/build.sh
@@ -283,24 +283,6 @@ mkdir -p "$DISTSRC"
             ;;
     esac
 
-    case "$HOST" in
-        *darwin*)
-            cmake --build build --target deploy ${V:+--verbose}
-            mv build/dist/Bitcoin-Core.zip "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.zip"
-            mkdir -p "unsigned-app-${HOST}"
-            cp  --target-directory="unsigned-app-${HOST}" \
-                contrib/macdeploy/detached-sig-create.sh
-            mv --target-directory="unsigned-app-${HOST}" build/dist
-            (
-                cd "unsigned-app-${HOST}"
-                find . -print0 \
-                    | sort --zero-terminated \
-                    | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
-                    | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
-                    || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
-            )
-            ;;
-    esac
     (
         cd installed
 
@@ -329,7 +311,7 @@ mkdir -p "$DISTSRC"
 
         cp -r "${DISTSRC}/share/rpcauth" "${DISTNAME}/share/"
 
-        # Finally, deterministically produce {non-,}debug binary tarballs ready
+        # Deterministically produce {non-,}debug binary tarballs ready
         # for release
         case "$HOST" in
             *mingw*)
@@ -337,8 +319,8 @@ mkdir -p "$DISTSRC"
                     | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
                 find "${DISTNAME}" -not -name "*.dbg" \
                     | sort \
-                    | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" \
-                    || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" && exit 1 )
+                    | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-unsigned.zip" \
+                    || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-unsigned.zip" && exit 1 )
                 find "${DISTNAME}" -name "*.dbg" -print0 \
                     | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
                 find "${DISTNAME}" -name "*.dbg" \
@@ -362,12 +344,13 @@ mkdir -p "$DISTSRC"
                 find "${DISTNAME}" -print0 \
                     | sort --zero-terminated \
                     | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
-                    | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
-                    || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
+                    | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
+                    || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
                 ;;
         esac
     )  # $DISTSRC/installed
 
+    # Finally make tarballs for codesigning
     case "$HOST" in
         *mingw*)
             cp -rf --target-directory=. contrib/windeploy
@@ -375,11 +358,31 @@ mkdir -p "$DISTSRC"
                 cd ./windeploy
                 mkdir -p unsigned
                 cp --target-directory=unsigned/ "${OUTDIR}/${DISTNAME}-win64-setup-unsigned.exe"
+                cp -r --target-directory=unsigned/ "${INSTALLPATH}"
+                find unsigned/ -name "*.dbg" -print0 \
+                    | xargs -0r rm
+                find . -print0 \
+                    | sort --zero-terminated \
+                    | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+                    | gzip -9n > "${OUTDIR}/${DISTNAME}-win64-codesigning.tar.gz" \
+                    || ( rm -f "${OUTDIR}/${DISTNAME}-win64-codesigning.tar.gz" && exit 1 )
+            )
+            ;;
+        *darwin*)
+            cmake --build build --target deploy ${V:+--verbose}
+            mv build/dist/Bitcoin-Core.zip "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.zip"
+            mkdir -p "unsigned-app-${HOST}"
+            cp  --target-directory="unsigned-app-${HOST}" \
+                contrib/macdeploy/detached-sig-create.sh
+            mv --target-directory="unsigned-app-${HOST}" build/dist
+            cp -r --target-directory="unsigned-app-${HOST}" "${INSTALLPATH}"
+            (
+                cd "unsigned-app-${HOST}"
                 find . -print0 \
                     | sort --zero-terminated \
                     | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
-                    | gzip -9n > "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" \
-                    || ( rm -f "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" && exit 1 )
+                    | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-codesigning.tar.gz" \
+                    || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-codesigning.tar.gz" && exit 1 )
             )
             ;;
     esac
diff --git a/contrib/guix/libexec/codesign.sh b/contrib/guix/libexec/codesign.sh
index b56d2a2309442..3a72937111441 100755
--- a/contrib/guix/libexec/codesign.sh
+++ b/contrib/guix/libexec/codesign.sh
@@ -4,6 +4,9 @@
 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
 export LC_ALL=C
 set -e -o pipefail
+
+# Environment variables for determinism
+export TAR_OPTIONS="--owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name"
 export TZ=UTC
 
 # Although Guix _does_ set umask when building its own packages (in our case,
@@ -27,7 +30,7 @@ fi
 # Check that required environment variables are set
 cat << EOF
 Required environment variables as seen inside the container:
-    UNSIGNED_TARBALL: ${UNSIGNED_TARBALL:?not set}
+    CODESIGNING_TARBALL: ${CODESIGNING_TARBALL:?not set}
     DETACHED_SIGS_REPO: ${DETACHED_SIGS_REPO:?not set}
     DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set}
     DISTNAME: ${DISTNAME:?not set}
@@ -63,27 +66,54 @@ mkdir -p "$DISTSRC"
 (
     cd "$DISTSRC"
 
-    tar -xf "$UNSIGNED_TARBALL"
+    tar -xf "$CODESIGNING_TARBALL"
 
     mkdir -p codesignatures
     tar -C codesignatures -xf "$CODESIGNATURE_GIT_ARCHIVE"
 
     case "$HOST" in
         *mingw*)
-            find "$PWD" -name "*-unsigned.exe" | while read -r infile; do
-                infile_base="$(basename "$infile")"
-
-                # Codesigned *-unsigned.exe and output to OUTDIR
+            # Apply detached codesignatures
+            WORKDIR=".tmp"
+            mkdir -p ${WORKDIR}
+            cp -r --target-directory="${WORKDIR}" "unsigned/${DISTNAME}"
+            find "${WORKDIR}/${DISTNAME}" -name "*.exe" -type f -exec rm {} \;
+            find unsigned/ -name "*.exe" -type f | while read -r bin
+            do
+                bin_base="$(realpath --relative-to=unsigned/ "${bin}")"
+                mkdir -p "${WORKDIR}/$(dirname "${bin_base}")"
                 osslsigncode attach-signature \
-                                 -in "$infile" \
-                                 -out "${OUTDIR}/${infile_base/-unsigned}" \
+                                 -in "${bin}" \
+                                 -out "${WORKDIR}/${bin_base/-unsigned}" \
                                  -CAfile "$GUIX_ENVIRONMENT/etc/ssl/certs/ca-certificates.crt" \
-                                 -sigin codesignatures/win/"$infile_base".pem
+                                 -sigin codesignatures/win/"${bin_base}".pem
             done
+
+            # Move installer to outdir
+            cd "${WORKDIR}"
+            find . -name "*setup.exe" -print0 \
+                | xargs -0r mv --target-directory="${OUTDIR}"
+
+            # Make .zip from binaries
+            find "${DISTNAME}" -print0 \
+                | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+            find "${DISTNAME}" \
+                | sort \
+                | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" \
+                || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" && exit 1 )
             ;;
         *darwin*)
-            # Apply detached codesignatures to dist/ (in-place)
-            signapple apply dist/Bitcoin-Qt.app codesignatures/osx/dist
+            case "$HOST" in
+                arm64*) ARCH="arm64" ;;
+                x86_64*) ARCH="x86_64" ;;
+            esac
+
+            # Apply detached codesignatures (in-place)
+            signapple apply dist/Bitcoin-Qt.app codesignatures/osx/"${HOST}"/dist/Bitcoin-Qt.app
+            find "${DISTNAME}" -wholename "*/bin/*" -type f | while read -r bin
+            do
+                signapple apply "${bin}" "codesignatures/osx/${HOST}/${bin}.${ARCH}sign"
+            done
 
             # Make a .zip from dist/
             cd dist/
@@ -91,6 +121,14 @@ mkdir -p "$DISTSRC"
                 | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
             find . | sort \
                 | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST}.zip"
+            cd ..
+
+            # Make a .tar.gz from bins
+            find "${DISTNAME}" -print0 \
+                | sort --zero-terminated \
+                | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+                | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
+                || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
             ;;
         *)
             exit 1
@@ -105,7 +143,7 @@ mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
 (
     cd /outdir-base
     {
-        echo "$UNSIGNED_TARBALL"
+        echo "$CODESIGNING_TARBALL"
         echo "$CODESIGNATURE_GIT_ARCHIVE"
         find "$ACTUAL_OUTDIR" -type f
     } | xargs realpath --relative-base="$PWD" \
diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm
index eccb408559605..73eef0a446b3b 100644
--- a/contrib/guix/manifest.scm
+++ b/contrib/guix/manifest.scm
@@ -15,13 +15,14 @@
              (gnu packages mingw)
              (gnu packages pkg-config)
              ((gnu packages python) #:select (python-minimal))
-             ((gnu packages python-build) #:select (python-tomli))
+             ((gnu packages python-build) #:select (python-tomli python-poetry-core))
              ((gnu packages python-crypto) #:select (python-asn1crypto))
              ((gnu packages tls) #:select (openssl))
              ((gnu packages version-control) #:select (git-minimal))
              (guix build-system cmake)
              (guix build-system gnu)
              (guix build-system python)
+             (guix build-system pyproject)
              (guix build-system trivial)
              (guix download)
              (guix gexp)
@@ -381,10 +382,10 @@ specific moment in time, whitelisting and revocation checks.")
       (license license:expat))))
 
 (define-public python-signapple
-  (let ((commit "62155712e7417aba07565c9780a80e452823ae6a"))
+  (let ((commit "85bfcecc33d2773bc09bc318cec0614af2c8e287"))
     (package
       (name "python-signapple")
-      (version (git-version "0.1" "1" commit))
+      (version (git-version "0.2.0" "1" commit))
       (source
        (origin
          (method git-fetch)
@@ -394,13 +395,14 @@ specific moment in time, whitelisting and revocation checks.")
          (file-name (git-file-name name commit))
          (sha256
           (base32
-           "1nm6rm4h4m7kbq729si4cm8rzild62mk4ni8xr5zja7l33fhv3gb"))))
-      (build-system python-build-system)
+           "17yqjll8nw83q6dhgqhkl7w502z5vy9sln8m6mlx0f1c10isg8yg"))))
+      (build-system pyproject-build-system)
       (propagated-inputs
         (list python-asn1crypto
               python-oscrypto
               python-certvalidator
               python-elfesteem))
+      (native-inputs (list python-poetry-core))
       ;; There are no tests, but attempting to run python setup.py test leads to
       ;; problems, just disable the test
       (arguments '(#:tests? #f))
diff --git a/contrib/macdeploy/detached-sig-create.sh b/contrib/macdeploy/detached-sig-create.sh
index 097a7c35ee451..89094403b7f7b 100755
--- a/contrib/macdeploy/detached-sig-create.sh
+++ b/contrib/macdeploy/detached-sig-create.sh
@@ -6,26 +6,57 @@
 export LC_ALL=C
 set -e
 
-ROOTDIR=dist
-BUNDLE="${ROOTDIR}/Bitcoin-Qt.app"
-BINARY="${BUNDLE}/Contents/MacOS/Bitcoin-Qt"
 SIGNAPPLE=signapple
 TEMPDIR=sign.temp
-ARCH=$(${SIGNAPPLE} info ${BINARY} | head -n 1 | cut -d " " -f 1)
+
+BUNDLE_ROOT=dist
+BUNDLE_NAME="Bitcoin-Qt.app"
+UNSIGNED_BUNDLE="${BUNDLE_ROOT}/${BUNDLE_NAME}"
+UNSIGNED_BINARY="${UNSIGNED_BUNDLE}/Contents/MacOS/Bitcoin-Qt"
+
+ARCH=$(${SIGNAPPLE} info ${UNSIGNED_BINARY} | head -n 1 | cut -d " " -f 1)
+
+OUTDIR="osx/${ARCH}-apple-darwin"
+OUTROOT="${TEMPDIR}/${OUTDIR}"
+
 OUT="signature-osx-${ARCH}.tar.gz"
-OUTROOT=osx/dist
 
-if [ -z "$1" ]; then
-  echo "usage: $0 <signapple args>"
-  echo "example: $0 <path to key>"
+if [ "$#" -ne 3 ]; then
+  echo "usage: $0 <path to key> <path to app store connect key> <apple developer team uuid>"
   exit 1
 fi
 
 rm -rf ${TEMPDIR}
 mkdir -p ${TEMPDIR}
 
-${SIGNAPPLE} sign -f --detach "${TEMPDIR}/${OUTROOT}"  "$@" "${BUNDLE}" --hardened-runtime
+stty -echo
+printf "Enter the passphrase for %s: " "$1"
+read cs_key_pass
+printf "\n"
+printf "Enter the passphrase for %s: " "$2"
+read api_key_pass
+printf "\n"
+stty echo
+
+# Sign and notarize app bundle
+${SIGNAPPLE} sign -f --hardened-runtime --detach "${OUTROOT}/${BUNDLE_ROOT}" --passphrase "${cs_key_pass}" "$1" "${UNSIGNED_BUNDLE}"
+${SIGNAPPLE} apply "${UNSIGNED_BUNDLE}" "${OUTROOT}/${BUNDLE_ROOT}/${BUNDLE_NAME}"
+${SIGNAPPLE} notarize --detach "${OUTROOT}/${BUNDLE_ROOT}" --passphrase "${api_key_pass}" "$2" "$3" "${UNSIGNED_BUNDLE}"
+
+# Sign each binary
+find . -maxdepth 3 -wholename "*/bin/*" -type f -exec realpath --relative-to=. {} \; | while read -r bin
+do
+    bin_dir=$(dirname "${bin}")
+    bin_name=$(basename "${bin}")
+    ${SIGNAPPLE} sign -f --hardened-runtime --detach "${OUTROOT}/${bin_dir}" --passphrase "${cs_key_pass}" "$1" "${bin}"
+    ${SIGNAPPLE} apply "${bin}" "${OUTROOT}/${bin_dir}/${bin_name}.${ARCH}sign"
+done
+
+# Notarize the binaries
+# Binaries cannot have stapled notarizations so this does not actually generate any output
+binaries_dir=$(dirname "$(find . -maxdepth 2 -wholename '*/bin' -type d -exec realpath --relative-to=. {} \;)")
+${SIGNAPPLE} notarize --passphrase "${api_key_pass}" "$2" "$3" "${binaries_dir}"
 
-tar -C "${TEMPDIR}" -czf "${OUT}" .
+tar -C "${TEMPDIR}" -czf "${OUT}" "${OUTDIR}"
 rm -rf "${TEMPDIR}"
 echo "Created ${OUT}"
diff --git a/contrib/windeploy/detached-sig-create.sh b/contrib/windeploy/detached-sig-create.sh
index 82fcf2d40684f..4ec726534c7c9 100755
--- a/contrib/windeploy/detached-sig-create.sh
+++ b/contrib/windeploy/detached-sig-create.sh
@@ -8,9 +8,9 @@ if [ -z "$OSSLSIGNCODE" ]; then
   OSSLSIGNCODE=osslsigncode
 fi
 
-if [ -z "$1" ]; then
-  echo "usage: $0 <osslcodesign args>"
-  echo "example: $0 -key codesign.key"
+if [ "$#" -ne 1 ]; then
+  echo "usage: $0 <path to key>"
+  echo "example: $0 codesign.key"
   exit 1
 fi
 
@@ -22,12 +22,22 @@ OUTSUBDIR="${OUTDIR}/win"
 TIMESERVER=http://timestamp.comodoca.com
 CERTFILE="win-codesign.cert"
 
+stty -echo
+printf "Enter the passphrase for %s: " "$1"
+read cs_key_pass
+printf "\n"
+stty echo
+
+
 mkdir -p "${OUTSUBDIR}"
-# shellcheck disable=SC2046
-basename -a $(ls -1 "${SRCDIR}"/*-unsigned.exe) | while read UNSIGNED; do
-  echo Signing "${UNSIGNED}"
-  "${OSSLSIGNCODE}" sign -certs "${CERTFILE}" -t "${TIMESERVER}" -h sha256 -in "${SRCDIR}/${UNSIGNED}" -out "${WORKDIR}/${UNSIGNED}" "$@"
-  "${OSSLSIGNCODE}" extract-signature -pem -in "${WORKDIR}/${UNSIGNED}" -out "${OUTSUBDIR}/${UNSIGNED}.pem" && rm "${WORKDIR}/${UNSIGNED}"
+find ${SRCDIR} -wholename "*.exe" -type f -exec realpath --relative-to=. {} \; | while read -r bin
+do
+    echo Signing "${bin}"
+    bin_base="$(realpath --relative-to=${SRCDIR} "${bin}")"
+    mkdir -p "$(dirname ${WORKDIR}/"${bin_base}")"
+    "${OSSLSIGNCODE}" sign -certs "${CERTFILE}" -t "${TIMESERVER}" -h sha256 -in "${bin}" -out "${WORKDIR}/${bin_base}" -key "$1" -pass "${cs_key_pass}"
+    mkdir -p "$(dirname ${OUTSUBDIR}/"${bin_base}")"
+    "${OSSLSIGNCODE}" extract-signature -pem -in "${WORKDIR}/${bin_base}" -out "${OUTSUBDIR}/${bin_base}.pem" && rm "${WORKDIR}/${bin_base}"
 done
 
 rm -f "${OUT}"
diff --git a/doc/release-process.md b/doc/release-process.md
index 197ed8c0a4edf..872da43bd3271 100644
--- a/doc/release-process.md
+++ b/doc/release-process.md
@@ -164,8 +164,8 @@ Then open a Pull Request to the [guix.sigs repository](https://github.com/bitcoi
 
 In the `guix-build-${VERSION}/output/x86_64-apple-darwin` and `guix-build-${VERSION}/output/arm64-apple-darwin` directories:
 
-    tar xf bitcoin-osx-unsigned.tar.gz
-    ./detached-sig-create.sh /path/to/codesign.p12
+    tar xf bitcoin-${VERSION}-${ARCH}-apple-darwin-codesigning.tar.gz
+    ./detached-sig-create.sh /path/to/codesign.p12 /path/to/AuthKey_foo.p8 uuid
     Enter the keychain password and authorize the signature
     signature-osx.tar.gz will be created
 
@@ -173,8 +173,8 @@ In the `guix-build-${VERSION}/output/x86_64-apple-darwin` and `guix-build-${VERS
 
 In the `guix-build-${VERSION}/output/x86_64-w64-mingw32` directory:
 
-    tar xf bitcoin-win-unsigned.tar.gz
-    ./detached-sig-create.sh -key /path/to/codesign.key
+    tar xf bitcoin-${VERSION}-win64-codesigning.tar.gz
+    ./detached-sig-create.sh /path/to/codesign.key
     Enter the passphrase for the key when prompted
     signature-win.tar.gz will be created
 


Test patch: