ci: scripts/sign-windows-bin.sh for signing Windows binaries

This introduces the `scripts/sign-windows-bin.sh` script which is used
by the `Makefile` to sign application libraries and executables. It also
implements the logic necessary to distinguish between different types of
builds: release and non-release builds.

Some other changes:

* Refactore the `Makefile` target that creates the Windows ZIP to make less verbose.
* Added `Microsoft.VisualStudio.Component.Windows10SDK.10240` to VisualStudio component
* Added `BUILD_TYPE` parameter to `Jenkinsfile`s for different platform builds

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2021-04-19 16:45:46 +02:00 committed by Jakub
parent 02b26f3c28
commit 089ee7ee80
8 changed files with 160 additions and 80 deletions

View File

@ -78,7 +78,6 @@ else ifeq ($(detected_OS),Windows)
PKG_TARGET := pkg-windows PKG_TARGET := pkg-windows
QRCODEGEN_MAKE_PARAMS := CC=gcc QRCODEGEN_MAKE_PARAMS := CC=gcc
RUN_TARGET := run-windows RUN_TARGET := run-windows
SIGNTOOL ?= C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.17763.0\\x64\\signtool.exe
VCINSTALLDIR ?= C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\ VCINSTALLDIR ?= C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\
export VCINSTALLDIR export VCINSTALLDIR
else else
@ -350,74 +349,40 @@ $(NIM_WINDOWS_PREBUILT_DLLS):
rm -rf tmp/windows rm -rf tmp/windows
mkdir -p tmp/windows/tools mkdir -p tmp/windows/tools
cd tmp/windows/tools && \ cd tmp/windows/tools && \
wget https://nim-lang.org/download/dlls.zip && \ wget -nv https://nim-lang.org/download/dlls.zip && \
unzip dlls.zip unzip dlls.zip
nim_windows_launcher: | deps nim_windows_launcher: | deps
$(ENV_SCRIPT) nim c -d:debug --outdir:./bin --passL:"-static-libgcc -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive" src/nim_windows_launcher.nim $(ENV_SCRIPT) nim c -d:debug --outdir:./bin --passL:"-static-libgcc -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive" src/nim_windows_launcher.nim
ifneq ($(WINDOWS_CODESIGN_TIMESTAMP_URL),)
WINDOWS_CODESIGN_TIMESTAMP_PARAM := -t $(WINDOWS_CODESIGN_TIMESTAMP_URL)
endif
STATUS_CLIENT_ZIP ?= pkg/Status.zip STATUS_CLIENT_ZIP ?= pkg/Status.zip
$(STATUS_CLIENT_ZIP): override RESOURCES_LAYOUT := -d:production $(STATUS_CLIENT_ZIP): override RESOURCES_LAYOUT := -d:production
$(STATUS_CLIENT_ZIP): OUTPUT := tmp/windows/dist/Status
$(STATUS_CLIENT_ZIP): nim_status_client nim_windows_launcher $(NIM_WINDOWS_PREBUILT_DLLS) $(STATUS_CLIENT_ZIP): nim_status_client nim_windows_launcher $(NIM_WINDOWS_PREBUILT_DLLS)
rm -rf pkg/*.zip rm -rf pkg/*.zip tmp/windows/dist
rm -rf tmp/windows/dist mkdir -p $(OUTPUT)/bin $(OUTPUT)/resources $(OUTPUT)/vendor $(OUTPUT)/resources/i18n
mkdir -p tmp/windows/dist/Status/bin cat windows-install.txt | unix2dos > $(OUTPUT)/INSTALL.txt
mkdir -p tmp/windows/dist/Status/resources cp status.ico status.svg resources.rcc $(FLEETS) $(OUTPUT)/resources/
mkdir -p tmp/windows/dist/Status/vendor cp ui/i18n/* $(OUTPUT)/resources/i18n
cp windows-install.txt tmp/windows/dist/Status/INSTALL.txt cp bin/nim_status_client.exe $(OUTPUT)/bin/Status.exe
unix2dos -k tmp/windows/dist/Status/INSTALL.txt cp bin/nim_windows_launcher.exe $(OUTPUT)/Status.exe
# cp LICENSE tmp/windows/dist/Status/LICENSE.txt rcedit $(OUTPUT)/bin/Status.exe --set-icon $(OUTPUT)/resources/status.ico
# unix2dos -k tmp/windows/dist/Status/LICENSE.txt rcedit $(OUTPUT)/Status.exe --set-icon $(OUTPUT)/resources/status.ico
cp status.ico tmp/windows/dist/Status/resources/ cp $(DOTHERSIDE) $(STATUSGO) tmp/windows/tools/*.dll $(OUTPUT)/bin/
cp status.svg tmp/windows/dist/Status/resources/ cp "$(shell which libgcc_s_seh-1.dll)" $(OUTPUT)/bin/
cp resources.rcc tmp/windows/dist/Status/resources/ cp "$(shell which libwinpthread-1.dll)" $(OUTPUT)/bin/
cp $(FLEETS) tmp/windows/dist/Status/resources/
cp bin/nim_status_client.exe tmp/windows/dist/Status/bin/Status.exe
cp bin/nim_windows_launcher.exe tmp/windows/dist/Status/Status.exe
rcedit \
tmp/windows/dist/Status/bin/Status.exe \
--set-icon tmp/windows/dist/Status/resources/status.ico
rcedit \
tmp/windows/dist/Status/Status.exe \
--set-icon tmp/windows/dist/Status/resources/status.ico
cp $(DOTHERSIDE) tmp/windows/dist/Status/bin/
cp $(STATUSGO) tmp/windows/dist/Status/bin/
cp tmp/windows/tools/*.dll tmp/windows/dist/Status/bin/
mkdir -p tmp/windows/dist/Status/resources/i18n
cp ui/i18n/* tmp/windows/dist/Status/resources/i18n
cp "$(shell which libgcc_s_seh-1.dll)" tmp/windows/dist/Status/bin/
cp "$(shell which libwinpthread-1.dll)" tmp/windows/dist/Status/bin/
echo -e $(BUILD_MSG) "deployable folder" echo -e $(BUILD_MSG) "deployable folder"
windeployqt \ windeployqt --compiler-runtime --qmldir ui --release \
--compiler-runtime \
--qmldir ui \
--release \
tmp/windows/dist/Status/bin/DOtherSide.dll tmp/windows/dist/Status/bin/DOtherSide.dll
mv tmp/windows/dist/Status/bin/vc_redist.x64.exe tmp/windows/dist/Status/vendor/ mv tmp/windows/dist/Status/bin/vc_redist.x64.exe tmp/windows/dist/Status/vendor/
# if WINDOWS_CODESIGN_PFX_PATH is not set then DLLs, EXEs are not signed
# if WINDOWS_CODESIGN_PFX_PATH is not set then DLLs, EXEs are not signed
ifdef WINDOWS_CODESIGN_PFX_PATH ifdef WINDOWS_CODESIGN_PFX_PATH
find ./tmp/windows/dist/Status -type f \ scripts/sign-windows-bin.sh ./tmp/windows/dist/Status
| /usr/bin/egrep -i "\.(dll|exe)$$" \
| /usr/bin/xargs -I{} /usr/bin/bash -c \
"if ! '$(SIGNTOOL)' verify -pa {} &>/dev/null; then echo {}; fi" \
| /usr/bin/xargs -I{} "$(SIGNTOOL)" \
sign \
-v \
-f $(WINDOWS_CODESIGN_PFX_PATH) \
$(WINDOWS_CODESIGN_TIMESTAMP_PARAM) \
{}
endif endif
echo -e $(BUILD_MSG) "zip" echo -e $(BUILD_MSG) "zip"
mkdir -p pkg mkdir -p pkg
cd tmp/windows/dist/Status && \ cd $(OUTPUT) && \
7z a ../../../../$(STATUS_CLIENT_ZIP) * 7z a ../../../../$(STATUS_CLIENT_ZIP) *
pkg: $(PKG_TARGET) pkg: $(PKG_TARGET)

View File

@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.2.13' library 'status-jenkins-lib@v1.2.15'
pipeline { pipeline {
agent { label 'linux' } agent { label 'linux' }

View File

@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.2.13' library 'status-jenkins-lib@v1.2.15'
pipeline { pipeline {
agent { agent {
@ -10,6 +10,14 @@ pipeline {
} }
} }
parameters {
string(
name: 'BUILD_TYPE',
description: 'Specify build type. Values: pr / nightly / release',
defaultValue: 'pr',
)
}
options { options {
timestamps() timestamps()
/* Prevent Jenkins jobs from running forever */ /* Prevent Jenkins jobs from running forever */

View File

@ -1,10 +1,18 @@
library 'status-jenkins-lib@v1.2.13' library 'status-jenkins-lib@v1.2.15'
pipeline { pipeline {
agent { agent {
label 'macos' label 'macos'
} }
parameters {
string(
name: 'BUILD_TYPE',
description: 'Specify build type. Values: pr / nightly / release',
defaultValue: 'pr',
)
}
options { options {
timestamps() timestamps()
/* Prevent Jenkins jobs from running forever */ /* Prevent Jenkins jobs from running forever */

View File

@ -1,8 +1,16 @@
library 'status-jenkins-lib@v1.2.13' library 'status-jenkins-lib@v1.2.15'
pipeline { pipeline {
agent { label 'windows' } agent { label 'windows' }
parameters {
string(
name: 'BUILD_TYPE',
description: 'Specify build type. Values: pr / nightly / release',
defaultValue: 'pr',
)
}
options { options {
timestamps() timestamps()
/* Prevent Jenkins jobs from running forever */ /* Prevent Jenkins jobs from running forever */
@ -21,23 +29,10 @@ pipeline {
MAKEFLAGS = '-j4' MAKEFLAGS = '-j4'
/* Disable colors in Nim compiler logs */ /* Disable colors in Nim compiler logs */
NIMFLAGS = '--colors:off' NIMFLAGS = '--colors:off'
/* Makefile assumes the compiler folder is included */
QTDIR = '/c/Qt/5.14.2/msvc2017_64'
Qt5_DIR = '/c/Qt/5.14.2/msvc2017_64'
VCINSTALLDIR = '/c/BuildTools/VC'
/* Control output the filename */ /* Control output the filename */
STATUS_CLIENT_ZIP = "pkg/${utils.pkgFilename('zip')}" STATUS_CLIENT_ZIP = "pkg/${utils.pkgFilename('zip')}"
/* Adjust path for correct access to build toos */ /* RFC 3161 timestamping URL for DigiCert */
PATH = [ WINDOWS_CODESIGN_TIMESTAMP_URL = 'http://timestamp.digicert.com'
"${QTDIR}/bin",
'/c/ProgramData/scoop/apps/gcc/current/bin',
'/c/ProgramData/scoop/apps/git/current/bin',
'/c/ProgramData/scoop/apps/git/current/usr/bin',
'/c/BuildTools/MSBuild/Current/Bin',
'/c/BuildTools/VC/Tools/MSVC/14.27.29110/bin',
'/c/ProgramData/scoop/shims',
'/c/ProgramData/scoop/apps/pypy3/current/Scripts',
].join(':')
} }
stages { stages {
@ -61,14 +56,9 @@ pipeline {
} }
stage('Package') { stage('Package') {
steps { steps { script {
withCredentials([string( windows.bundle()
credentialsId: utils.getInfuraTokenCred(), } }
variable: 'INFURA_TOKEN'
)]) {
sh "make ${env.STATUS_CLIENT_ZIP}"
}
}
} }
stage('Parallel Upload') { stage('Parallel Upload') {

59
docs/windows_signing.md Normal file
View File

@ -0,0 +1,59 @@
# Description
This document describes how the signing of Windows application was configured.
# Certificates
The signing uses two types of Certificates:
* Self-Signed Code Signing certificate for development and PR builds
* [DigiCert](https://www.digicert.com/) standard release Code Signing certificate
## Self-Signed Certificate
This certificate was created on using the following PowerShell commands:
```Powershell
$cert = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname status.im -Subject "Dev Status Cert" -type CodeSigning
$pwd = ConvertTo-SecureString -String 'SUPER-SECRET-PASSWORD -Force -AsPlainText
Export-PfxCertificate -cert $cert -FilePath Status-Destkop-SelfSigned.pfx -Password $pwd -CryptoAlgorithmOption AES256_SHA256
```
Which should create a `Status-Destkop-SelfSigned.pfx` file encrypted with the provided password.
Keep in mind that the `-type CodeSigning` flag is important.
For more details see [this article](http://woshub.com/how-to-create-self-signed-certificate-with-powershell/).
## DigiCert Certificate
This certificate is was purchased on 23rd of September 2020 from [DigiCert.com](https://www.digicert.com/).
It is a `Microsoft Authenticode` certificate and should be valid for 2 years.
# Continuous Integration
The Jenkins setup which makes use of these certificates makes them available under different job folder under different credential names. This way we can sign non-release builds while not making them appear to a user as a release build. The self-signed certificate should trigger windows warnings when starting the application.
The way this works is certificates are split across two Jenkins job folders:
* [status-desktop/platforms](https://ci.status.im/job/status-desktop/job/platforms/credentials/store/folder/domain/_/) - Release and Nightly builds.
* [status-desktop/branches](https://ci.status.im/job/status-desktop/job/branches/credentials/store/folder/domain/_/) - Branch and PR builds.
These folders contain different certificates, which provides another layer of security in case someone submits a malicious PR which attempts to extract valuable secrets. In this setup the only thing they might possibly extract would be the self-signed certificate and its password.
The exact access to the credentials is hidden from malicious eyes that can inspect `Jenkinsfile`s in this repo, and instead are implemented in our private [`status-jenkins-lib`](https://github.com/status-im/status-jenkins-lib) repository under `vars/windows.groovy`.
# Known Issues
#### `Error: Store::ImportCertObject() failed. (-2146893808/0x80090010)`
This error would appears when trying to sign binaries with `signtool.exe` when Jenkins was accessing the Windows CI slave via SSH.
The solution was to switch the setup to deploy the [Jenkins Remoting Agent Service](https://www.jenkins.io/projects/remoting/) using the [WinSW](https://github.com/winsw/winsw) utility to run it as a Windows service.
#### `CertEnroll::CX509Enrollment::_CreateRequest: Access denied. 0x80090010 (-2146893808 NTE_PERM)`
You cannot create a self-signed certificate in a PowerShell instance without elevated privilidges. You might have to run the shell as system administrator.
# Links
* https://github.com/status-im/infra-ci/issues/28
* https://github.com/status-im/status-desktop/issues/2170

49
scripts/sign-windows-bin.sh Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -eof pipefail
if [[ $# -ne 1 ]]; then
echo "No path to search for EXE and DLL files provided!" >&2
exit 1
fi
function must_get_env() {
declare -n VAR_VALUE="$1"
if [[ -z "${VAR_VALUE}" ]]; then
echo -e "Missing env variable: ${!VAR_VALUE}" 1>&2
exit 1
fi
}
# The signing certificate, password, and timestamp server is required.
must_get_env WINDOWS_CODESIGN_PFX_PATH
must_get_env WINDOWS_CODESIGN_PASSWORD
must_get_env WINDOWS_CODESIGN_TIMESTAMP_URL
# Signing Tool usually comes with the Windows Kits.
WINDOWS_KITS='/c/Program Files (x86)/Windows Kits'
SIGNTOOL=$(find "${WINDOWS_KITS}" -iname 'signtool.exe' | grep x64 | sort | head -n1)
if [[ -z "${SIGNTOOL}" ]]; then
echo "No signtool.exe was found in '${WINDOWS_KITS}'!" >&2
exit 1
fi
# Find the files to sign.
FOUND_FILES=$(find "${1}" -type f -iname '*.dll' -or -iname '*.exe')
declare -a FILES_TO_SIGN
for FILE in ${FOUND_FILES}; do
# Some files like Qt libraries are already signed.
if "${SIGNTOOL}" verify -pa ${FILE} &>/dev/null; then
continue
fi
FILES_TO_SIGN+=("${FILE}")
done
# Sign all the non-signed binaries. Add -debug if need be.
"${SIGNTOOL}" sign -v -fd SHA256 \
-p "${WINDOWS_CODESIGN_PASSWORD}" \
-f "${WINDOWS_CODESIGN_PFX_PATH}" \
-tr "${WINDOWS_CODESIGN_TIMESTAMP_URL}" \
"${FILES_TO_SIGN[@]}" | dos2unix
echo "Signed successfully!"

View File

@ -42,6 +42,7 @@ function Install-VC-BuildTools {
"--add", "Microsoft.VisualStudio.Workload.VCTools", "--add", "Microsoft.VisualStudio.Workload.VCTools",
"--add", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "--add", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"--add", "Microsoft.VisualStudio.Component.VC.Redist.14.Latest", "--add", "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"--add", "Microsoft.VisualStudio.Component.Windows10SDK.10240",
"--add", "Microsoft.VisualStudio.Component.Windows10SDK.14393", "--add", "Microsoft.VisualStudio.Component.Windows10SDK.14393",
"--add", "Microsoft.VisualStudio.Component.Windows81SDK", "--add", "Microsoft.VisualStudio.Component.Windows81SDK",
"--add", "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Win81" "--add", "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Win81"