Skip to content

Commit bd0ee07

Browse files
committed
Merge bitcoin#31407: guix: Notarize MacOS app bundle and codesign all MacOS and Windows binaries
e181bda guix: Apply all codesignatures to Windows binaries (Ava Chow) aafbd23 guix: Apply codesignatures to all MacOS binaries (Ava Chow) 3656b82 contrib: Sign all Windows binaries too (Ava Chow) 31d3254 contrib: Sign and notarize all MacOS binaries (Ava Chow) 710d5b5 guix: Update signapple (Ava Chow) e8b3c44 build: Include all Windows binaries for codesigning (Ava Chow) dd4ec84 build: Include all MacOS binaries for codesigning (Ava Chow) 4e5c9ce guix: Rename Windows unsigned binaries to unsigned.zip (Ava Chow) d9d49cd guix: Rename MacOS binaries to unsigned.tar.gz (Ava Chow) c214e52 guix: Rename unsigned.tar.gz to codesigning.tar.gz (Ava Chow) Pull request description: I have updated signapple to notarize MacOS app bundles without adding any additional dependencies. Further, it can also sign and apply detached signatures to standalone binaries. As such, we can use signapple to perform the notarization and stapling steps so that MacOS will run the app bundle after it is installed. `detached-sig-create.sh` is updated to have a notarization step and to download the ticket which will be included in the detached signatures. The workflow is largely unchanged for the MacOS codesigners except for the additional requirement of having an App Store Connect API key and Team UUID, instructions for which can be found at https://github.com/achow101/signapple/blob/master/docs/notarization.md. For guix builders, the workflow is unchanged. Additionally, the standalone binaries packaged in the MacOS `.tar.gz` and Windows `.zip` will now be codesigned. `detached-sig-create.sh` was updated to handle these, so the workflow for both MacOS and Windows codesigners remains unchanged. For guix builders, the workflow is also unchanged. Because those binaries will how have codesigned and unsigned versions, the build command is modified to output `-unsigned.{tar.gz,zip}` archives containing the binaries. Since this happens to conflict with the tarball used for codesigning, the codesigning tarball was renamed to `-codesigning.tar.gz`. Both MacOS and Windows codesigners will need to adjust their workflows to account for the new name. Fixes bitcoin#15774 and bitcoin#29749 ACKs for top commit: Sjors: Tested ACK e181bda davidgumberg: Tested ACK bitcoin@e181bda. pinheadmz: tested ACK e181bda Tree-SHA512: ce0e2bf38e1748cdaa0d13be6f61c3289cd09cfb7d071a68b0b13d2802b3936c9112eda6e4c7b29c535c0995d56b14871442589cdcea2e7707e35c1b278b9263
2 parents 0391d7e + e181bda commit bd0ee07

File tree

7 files changed

+160
-76
lines changed

7 files changed

+160
-76
lines changed

contrib/guix/guix-codesign

+12-12
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ fi
137137

138138

139139
################
140-
# Unsigned tarballs SHOULD exist
140+
# Codesigning tarballs SHOULD exist
141141
################
142142

143143
# Usage: outdir_for_host HOST SUFFIX
@@ -149,13 +149,13 @@ outdir_for_host() {
149149
}
150150

151151

152-
unsigned_tarball_for_host() {
152+
codesigning_tarball_for_host() {
153153
case "$1" in
154154
*mingw*)
155-
echo "$(outdir_for_host "$1")/${DISTNAME}-win64-unsigned.tar.gz"
155+
echo "$(outdir_for_host "$1")/${DISTNAME}-win64-codesigning.tar.gz"
156156
;;
157157
*darwin*)
158-
echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-unsigned.tar.gz"
158+
echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-codesigning.tar.gz"
159159
;;
160160
*)
161161
exit 1
@@ -164,22 +164,22 @@ unsigned_tarball_for_host() {
164164
}
165165

166166
# Accumulate a list of build directories that already exist...
167-
hosts_unsigned_tarball_missing=""
167+
hosts_codesigning_tarball_missing=""
168168
for host in $HOSTS; do
169-
if [ ! -e "$(unsigned_tarball_for_host "$host")" ]; then
170-
hosts_unsigned_tarball_missing+=" ${host}"
169+
if [ ! -e "$(codesigning_tarball_for_host "$host")" ]; then
170+
hosts_codesigning_tarball_missing+=" ${host}"
171171
fi
172172
done
173173

174-
if [ -n "$hosts_unsigned_tarball_missing" ]; then
174+
if [ -n "$hosts_codesigning_tarball_missing" ]; then
175175
# ...so that we can print them out nicely in an error message
176176
cat << EOF
177-
ERR: Unsigned tarballs do not exist
177+
ERR: Codesigning tarballs do not exist
178178
...
179179
180180
EOF
181-
for host in $hosts_unsigned_tarball_missing; do
182-
echo " ${host} '$(unsigned_tarball_for_host "$host")'"
181+
for host in $hosts_codesigning_tarball_missing; do
182+
echo " ${host} '$(codesigning_tarball_for_host "$host")'"
183183
done
184184
exit 1
185185
fi
@@ -371,7 +371,7 @@ EOF
371371
OUTDIR="$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST" codesigned)" \
372372
DIST_ARCHIVE_BASE=/outdir-base/dist-archive \
373373
DETACHED_SIGS_REPO=/detached-sigs \
374-
UNSIGNED_TARBALL="$(OUTDIR_BASE=/outdir-base && unsigned_tarball_for_host "$HOST")" \
374+
CODESIGNING_TARBALL="$(OUTDIR_BASE=/outdir-base && codesigning_tarball_for_host "$HOST")" \
375375
bash -c "cd /bitcoin && bash contrib/guix/libexec/codesign.sh"
376376
)
377377

contrib/guix/libexec/build.sh

+28-25
Original file line numberDiff line numberDiff line change
@@ -281,24 +281,6 @@ mkdir -p "$DISTSRC"
281281
;;
282282
esac
283283

284-
case "$HOST" in
285-
*darwin*)
286-
cmake --build build --target deploy ${V:+--verbose}
287-
mv build/dist/Bitcoin-Core.zip "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.zip"
288-
mkdir -p "unsigned-app-${HOST}"
289-
cp --target-directory="unsigned-app-${HOST}" \
290-
contrib/macdeploy/detached-sig-create.sh
291-
mv --target-directory="unsigned-app-${HOST}" build/dist
292-
(
293-
cd "unsigned-app-${HOST}"
294-
find . -print0 \
295-
| sort --zero-terminated \
296-
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
297-
| gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
298-
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
299-
)
300-
;;
301-
esac
302284
(
303285
cd installed
304286

@@ -327,16 +309,16 @@ mkdir -p "$DISTSRC"
327309

328310
cp -r "${DISTSRC}/share/rpcauth" "${DISTNAME}/share/"
329311

330-
# Finally, deterministically produce {non-,}debug binary tarballs ready
312+
# Deterministically produce {non-,}debug binary tarballs ready
331313
# for release
332314
case "$HOST" in
333315
*mingw*)
334316
find "${DISTNAME}" -not -name "*.dbg" -print0 \
335317
| xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
336318
find "${DISTNAME}" -not -name "*.dbg" \
337319
| sort \
338-
| zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" \
339-
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" && exit 1 )
320+
| zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-unsigned.zip" \
321+
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-unsigned.zip" && exit 1 )
340322
find "${DISTNAME}" -name "*.dbg" -print0 \
341323
| xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
342324
find "${DISTNAME}" -name "*.dbg" \
@@ -360,24 +342,45 @@ mkdir -p "$DISTSRC"
360342
find "${DISTNAME}" -print0 \
361343
| sort --zero-terminated \
362344
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
363-
| gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
364-
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
345+
| gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
346+
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
365347
;;
366348
esac
367349
) # $DISTSRC/installed
368350

351+
# Finally make tarballs for codesigning
369352
case "$HOST" in
370353
*mingw*)
371354
cp -rf --target-directory=. contrib/windeploy
372355
(
373356
cd ./windeploy
374357
mkdir -p unsigned
375358
cp --target-directory=unsigned/ "${OUTDIR}/${DISTNAME}-win64-setup-unsigned.exe"
359+
cp -r --target-directory=unsigned/ "${INSTALLPATH}"
360+
find unsigned/ -name "*.dbg" -print0 \
361+
| xargs -0r rm
362+
find . -print0 \
363+
| sort --zero-terminated \
364+
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
365+
| gzip -9n > "${OUTDIR}/${DISTNAME}-win64-codesigning.tar.gz" \
366+
|| ( rm -f "${OUTDIR}/${DISTNAME}-win64-codesigning.tar.gz" && exit 1 )
367+
)
368+
;;
369+
*darwin*)
370+
cmake --build build --target deploy ${V:+--verbose}
371+
mv build/dist/Bitcoin-Core.zip "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.zip"
372+
mkdir -p "unsigned-app-${HOST}"
373+
cp --target-directory="unsigned-app-${HOST}" \
374+
contrib/macdeploy/detached-sig-create.sh
375+
mv --target-directory="unsigned-app-${HOST}" build/dist
376+
cp -r --target-directory="unsigned-app-${HOST}" "${INSTALLPATH}"
377+
(
378+
cd "unsigned-app-${HOST}"
376379
find . -print0 \
377380
| sort --zero-terminated \
378381
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
379-
| gzip -9n > "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" \
380-
|| ( rm -f "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" && exit 1 )
382+
| gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-codesigning.tar.gz" \
383+
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-codesigning.tar.gz" && exit 1 )
381384
)
382385
;;
383386
esac

contrib/guix/libexec/codesign.sh

+50-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
export LC_ALL=C
66
set -e -o pipefail
7+
8+
# Environment variables for determinism
9+
export TAR_OPTIONS="--owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name"
710
export TZ=UTC
811

912
# Although Guix _does_ set umask when building its own packages (in our case,
@@ -27,7 +30,7 @@ fi
2730
# Check that required environment variables are set
2831
cat << EOF
2932
Required environment variables as seen inside the container:
30-
UNSIGNED_TARBALL: ${UNSIGNED_TARBALL:?not set}
33+
CODESIGNING_TARBALL: ${CODESIGNING_TARBALL:?not set}
3134
DETACHED_SIGS_REPO: ${DETACHED_SIGS_REPO:?not set}
3235
DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set}
3336
DISTNAME: ${DISTNAME:?not set}
@@ -63,34 +66,69 @@ mkdir -p "$DISTSRC"
6366
(
6467
cd "$DISTSRC"
6568

66-
tar -xf "$UNSIGNED_TARBALL"
69+
tar -xf "$CODESIGNING_TARBALL"
6770

6871
mkdir -p codesignatures
6972
tar -C codesignatures -xf "$CODESIGNATURE_GIT_ARCHIVE"
7073

7174
case "$HOST" in
7275
*mingw*)
73-
find "$PWD" -name "*-unsigned.exe" | while read -r infile; do
74-
infile_base="$(basename "$infile")"
75-
76-
# Codesigned *-unsigned.exe and output to OUTDIR
76+
# Apply detached codesignatures
77+
WORKDIR=".tmp"
78+
mkdir -p ${WORKDIR}
79+
cp -r --target-directory="${WORKDIR}" "unsigned/${DISTNAME}"
80+
find "${WORKDIR}/${DISTNAME}" -name "*.exe" -type f -exec rm {} \;
81+
find unsigned/ -name "*.exe" -type f | while read -r bin
82+
do
83+
bin_base="$(realpath --relative-to=unsigned/ "${bin}")"
84+
mkdir -p "${WORKDIR}/$(dirname "${bin_base}")"
7785
osslsigncode attach-signature \
78-
-in "$infile" \
79-
-out "${OUTDIR}/${infile_base/-unsigned}" \
86+
-in "${bin}" \
87+
-out "${WORKDIR}/${bin_base/-unsigned}" \
8088
-CAfile "$GUIX_ENVIRONMENT/etc/ssl/certs/ca-certificates.crt" \
81-
-sigin codesignatures/win/"$infile_base".pem
89+
-sigin codesignatures/win/"${bin_base}".pem
8290
done
91+
92+
# Move installer to outdir
93+
cd "${WORKDIR}"
94+
find . -name "*setup.exe" -print0 \
95+
| xargs -0r mv --target-directory="${OUTDIR}"
96+
97+
# Make .zip from binaries
98+
find "${DISTNAME}" -print0 \
99+
| xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
100+
find "${DISTNAME}" \
101+
| sort \
102+
| zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" \
103+
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" && exit 1 )
83104
;;
84105
*darwin*)
85-
# Apply detached codesignatures to dist/ (in-place)
86-
signapple apply dist/Bitcoin-Qt.app codesignatures/osx/dist
106+
case "$HOST" in
107+
arm64*) ARCH="arm64" ;;
108+
x86_64*) ARCH="x86_64" ;;
109+
esac
110+
111+
# Apply detached codesignatures (in-place)
112+
signapple apply dist/Bitcoin-Qt.app codesignatures/osx/"${HOST}"/dist/Bitcoin-Qt.app
113+
find "${DISTNAME}" -wholename "*/bin/*" -type f | while read -r bin
114+
do
115+
signapple apply "${bin}" "codesignatures/osx/${HOST}/${bin}.${ARCH}sign"
116+
done
87117

88118
# Make a .zip from dist/
89119
cd dist/
90120
find . -print0 \
91121
| xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
92122
find . | sort \
93123
| zip -X@ "${OUTDIR}/${DISTNAME}-${HOST}.zip"
124+
cd ..
125+
126+
# Make a .tar.gz from bins
127+
find "${DISTNAME}" -print0 \
128+
| sort --zero-terminated \
129+
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
130+
| gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
131+
|| ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
94132
;;
95133
*)
96134
exit 1
@@ -105,7 +143,7 @@ mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
105143
(
106144
cd /outdir-base
107145
{
108-
echo "$UNSIGNED_TARBALL"
146+
echo "$CODESIGNING_TARBALL"
109147
echo "$CODESIGNATURE_GIT_ARCHIVE"
110148
find "$ACTUAL_OUTDIR" -type f
111149
} | xargs realpath --relative-base="$PWD" \

contrib/guix/manifest.scm

+7-5
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
(gnu packages mingw)
1616
(gnu packages pkg-config)
1717
((gnu packages python) #:select (python-minimal))
18-
((gnu packages python-build) #:select (python-tomli))
18+
((gnu packages python-build) #:select (python-tomli python-poetry-core))
1919
((gnu packages python-crypto) #:select (python-asn1crypto))
2020
((gnu packages tls) #:select (openssl))
2121
((gnu packages version-control) #:select (git-minimal))
2222
(guix build-system cmake)
2323
(guix build-system gnu)
2424
(guix build-system python)
25+
(guix build-system pyproject)
2526
(guix build-system trivial)
2627
(guix download)
2728
(guix gexp)
@@ -381,10 +382,10 @@ specific moment in time, whitelisting and revocation checks.")
381382
(license license:expat))))
382383

383384
(define-public python-signapple
384-
(let ((commit "62155712e7417aba07565c9780a80e452823ae6a"))
385+
(let ((commit "85bfcecc33d2773bc09bc318cec0614af2c8e287"))
385386
(package
386387
(name "python-signapple")
387-
(version (git-version "0.1" "1" commit))
388+
(version (git-version "0.2.0" "1" commit))
388389
(source
389390
(origin
390391
(method git-fetch)
@@ -394,13 +395,14 @@ specific moment in time, whitelisting and revocation checks.")
394395
(file-name (git-file-name name commit))
395396
(sha256
396397
(base32
397-
"1nm6rm4h4m7kbq729si4cm8rzild62mk4ni8xr5zja7l33fhv3gb"))))
398-
(build-system python-build-system)
398+
"17yqjll8nw83q6dhgqhkl7w502z5vy9sln8m6mlx0f1c10isg8yg"))))
399+
(build-system pyproject-build-system)
399400
(propagated-inputs
400401
(list python-asn1crypto
401402
python-oscrypto
402403
python-certvalidator
403404
python-elfesteem))
405+
(native-inputs (list python-poetry-core))
404406
;; There are no tests, but attempting to run python setup.py test leads to
405407
;; problems, just disable the test
406408
(arguments '(#:tests? #f))

contrib/macdeploy/detached-sig-create.sh

+41-10
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,57 @@
66
export LC_ALL=C
77
set -e
88

9-
ROOTDIR=dist
10-
BUNDLE="${ROOTDIR}/Bitcoin-Qt.app"
11-
BINARY="${BUNDLE}/Contents/MacOS/Bitcoin-Qt"
129
SIGNAPPLE=signapple
1310
TEMPDIR=sign.temp
14-
ARCH=$(${SIGNAPPLE} info ${BINARY} | head -n 1 | cut -d " " -f 1)
11+
12+
BUNDLE_ROOT=dist
13+
BUNDLE_NAME="Bitcoin-Qt.app"
14+
UNSIGNED_BUNDLE="${BUNDLE_ROOT}/${BUNDLE_NAME}"
15+
UNSIGNED_BINARY="${UNSIGNED_BUNDLE}/Contents/MacOS/Bitcoin-Qt"
16+
17+
ARCH=$(${SIGNAPPLE} info ${UNSIGNED_BINARY} | head -n 1 | cut -d " " -f 1)
18+
19+
OUTDIR="osx/${ARCH}-apple-darwin"
20+
OUTROOT="${TEMPDIR}/${OUTDIR}"
21+
1522
OUT="signature-osx-${ARCH}.tar.gz"
16-
OUTROOT=osx/dist
1723

18-
if [ -z "$1" ]; then
19-
echo "usage: $0 <signapple args>"
20-
echo "example: $0 <path to key>"
24+
if [ "$#" -ne 3 ]; then
25+
echo "usage: $0 <path to key> <path to app store connect key> <apple developer team uuid>"
2126
exit 1
2227
fi
2328

2429
rm -rf ${TEMPDIR}
2530
mkdir -p ${TEMPDIR}
2631

27-
${SIGNAPPLE} sign -f --detach "${TEMPDIR}/${OUTROOT}" "$@" "${BUNDLE}" --hardened-runtime
32+
stty -echo
33+
printf "Enter the passphrase for %s: " "$1"
34+
read cs_key_pass
35+
printf "\n"
36+
printf "Enter the passphrase for %s: " "$2"
37+
read api_key_pass
38+
printf "\n"
39+
stty echo
40+
41+
# Sign and notarize app bundle
42+
${SIGNAPPLE} sign -f --hardened-runtime --detach "${OUTROOT}/${BUNDLE_ROOT}" --passphrase "${cs_key_pass}" "$1" "${UNSIGNED_BUNDLE}"
43+
${SIGNAPPLE} apply "${UNSIGNED_BUNDLE}" "${OUTROOT}/${BUNDLE_ROOT}/${BUNDLE_NAME}"
44+
${SIGNAPPLE} notarize --detach "${OUTROOT}/${BUNDLE_ROOT}" --passphrase "${api_key_pass}" "$2" "$3" "${UNSIGNED_BUNDLE}"
45+
46+
# Sign each binary
47+
find . -maxdepth 3 -wholename "*/bin/*" -type f -exec realpath --relative-to=. {} \; | while read -r bin
48+
do
49+
bin_dir=$(dirname "${bin}")
50+
bin_name=$(basename "${bin}")
51+
${SIGNAPPLE} sign -f --hardened-runtime --detach "${OUTROOT}/${bin_dir}" --passphrase "${cs_key_pass}" "$1" "${bin}"
52+
${SIGNAPPLE} apply "${bin}" "${OUTROOT}/${bin_dir}/${bin_name}.${ARCH}sign"
53+
done
54+
55+
# Notarize the binaries
56+
# Binaries cannot have stapled notarizations so this does not actually generate any output
57+
binaries_dir=$(dirname "$(find . -maxdepth 2 -wholename '*/bin' -type d -exec realpath --relative-to=. {} \;)")
58+
${SIGNAPPLE} notarize --passphrase "${api_key_pass}" "$2" "$3" "${binaries_dir}"
2859

29-
tar -C "${TEMPDIR}" -czf "${OUT}" .
60+
tar -C "${TEMPDIR}" -czf "${OUT}" "${OUTDIR}"
3061
rm -rf "${TEMPDIR}"
3162
echo "Created ${OUT}"

0 commit comments

Comments
 (0)