From c4be51bde9d87f73c9bbeaf2472810e3792c1a66 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 6 Dec 2024 08:13:27 +0100 Subject: [PATCH] PICARD-3002: Use Azure Trusted Signing for code signing # Conflicts: # .github/workflows/package-windows.yml # scripts/package/win-common.ps1 --- .github/workflows/package-windows.yml | 127 ++++++++++------------ scripts/package/win-build-fixes.ps1 | 18 +++ scripts/package/win-common.ps1 | 47 -------- scripts/package/win-package-appx.ps1 | 60 +--------- scripts/package/win-package-installer.ps1 | 43 -------- scripts/package/win-package-portable.ps1 | 39 ------- 6 files changed, 83 insertions(+), 251 deletions(-) create mode 100644 scripts/package/win-build-fixes.ps1 delete mode 100644 scripts/package/win-package-installer.ps1 delete mode 100644 scripts/package/win-package-portable.ps1 diff --git a/.github/workflows/package-windows.yml b/.github/workflows/package-windows.yml index c5a36d4e40..a2f037a2a3 100644 --- a/.github/workflows/package-windows.yml +++ b/.github/workflows/package-windows.yml @@ -9,14 +9,15 @@ jobs: runs-on: windows-2019 strategy: matrix: - type: - - store-app - - signed-app - - installer - - portable + setup: + - type: installer + - type: portable + build-portable: 1 + - type: store-app + disable-autoupdate: 1 fail-fast: false env: - CODESIGN: 0 + CODESIGN: ${{ !!secrets.AZURE_CERT_PROFILE_NAME }} steps: - uses: actions/checkout@v4 with: @@ -32,9 +33,6 @@ jobs: -DiscidVersion $Env:DISCID_VERSION -DiscidSha256Sum $Env:DISCID_SHA256SUM ` -FpcalcVersion $Env:FPCALC_VERSION -FpcalcSha256Sum $Env:FPCALC_SHA256SUM Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64" - $ReleaseTag = $(git describe --match "release-*" --abbrev=0 --always HEAD) - $BuildNumber = $(git rev-list --count "$ReleaseTag..HEAD") - Add-Content $env:GITHUB_ENV "BUILD_NUMBER=$BuildNumber" New-Item -Name .\artifacts -ItemType Directory env: DISCID_VERSION: 0.6.4 @@ -57,80 +55,73 @@ jobs: - name: Patch build version if: startsWith(github.ref, 'refs/tags/') != true run: | + $ReleaseTag = $(git describe --match "release-*" --abbrev=0 --always HEAD) + $BuildNumber = $(git rev-list --count "$ReleaseTag..HEAD") python setup.py patch_version --platform=$Env:BUILD_NUMBER.$(git rev-parse --short HEAD) - name: Run tests timeout-minutes: 30 run: python setup.py test - - name: Prepare code signing certificate - if: matrix.type != 'store-app' + - name: Prepare clean build environment run: | - If ($Env:CODESIGN_P12_URL -And $Env:AWS_ACCESS_KEY_ID) { - pip install awscli - aws s3 cp "$Env:CODESIGN_P12_URL" .\codesign.pfx - Add-Content $env:GITHUB_ENV "CODESIGN=1" - } Else { - Write-Output "::warning::No code signing certificate available, skipping code signing." - } - env: - AWS_DEFAULT_REGION: eu-central-1 - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - CODESIGN_P12_URL: ${{ secrets.CODESIGN_P12_URL }} - - name: Build Windows 10 store app package - if: matrix.type == 'store-app' + Remove-Item -Path build,dist/picard,locale -Recurse -ErrorAction Ignore + python setup.py clean + - name: Build run: | - & .\scripts\package\win-package-appx.ps1 -BuildNumber $Env:BUILD_NUMBER - Move-Item .\dist\*.msix .\artifacts + python setup.py build --build-number=$BuildNumber + python setup.py build_ext + pyinstaller --noconfirm --clean picard.spec + If ($env:PICARD_BUILD_PORTABLE -ne "1") { + dist\picard\fpcalc -version + } env: PICARD_APPX_PUBLISHER: CN=0A9169B7-05A3-4ED9-8876-830F17846709 - - name: Build Windows 10 signed app package - if: matrix.type == 'signed-app' && env.CODESIGN == '1' - run: | - $CertificateFile = ".\codesign.pfx" - $CertificatePassword = ConvertTo-SecureString -String $Env:CODESIGN_P12_PASSWORD -Force -AsPlainText - & .\scripts\package\win-package-appx.ps1 -BuildNumber $Env:BUILD_NUMBER ` - -CertificateFile $CertificateFile -CertificatePassword $CertificatePassword - Move-Item .\dist\*.msix .\artifacts - env: - CODESIGN_P12_PASSWORD: ${{ secrets.CODESIGN_P12_PASSWORD }} + PICARD_BUILD_PORTABLE: ${{ matrix.setup.build-portable }} + PICARD_DISABLE_AUTOUPDATE: ${{ matrix.setup.disable-autoupdate }} + - name: Sign picard.exe + uses: azure/trusted-signing-action@v0.5.0 + if: matrix.setup.type != 'portable' && env.CODESIGN == 'true' + with: + azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} + azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} + azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} + endpoint: ${{ secrets.AZURE_ENDPOINT }} + trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }} + certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }} + files-folder: dist\picard + files-folder-filter: exe + files-folder-recurse: true + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 - name: Build Windows installer - if: matrix.type == 'installer' + if: matrix.setup.type == 'installer' run: | - # choco install nsis - If ($Env:CODESIGN -eq "1") { - $CertificateFile = ".\codesign.pfx" - $CertificatePassword = ConvertTo-SecureString -String $Env:CODESIGN_P12_PASSWORD -Force -AsPlainText - } Else { - $CertificateFile = $null - $CertificatePassword = $null - } - & .\scripts\package\win-package-installer.ps1 -BuildNumber $Env:BUILD_NUMBER ` - -CertificateFile $CertificateFile -CertificatePassword $CertificatePassword + makensis.exe /INPUTCHARSET UTF8 installer\picard-setup.nsi Move-Item .\installer\*.exe .\artifacts - dist\picard\fpcalc -version - env: - CODESIGN_P12_PASSWORD: ${{ secrets.CODESIGN_P12_PASSWORD }} - name: Build Windows portable app - if: matrix.type == 'portable' + if: matrix.setup.type == 'portable' run: | - If ($Env:CODESIGN -eq "1") { - $CertificateFile = ".\codesign.pfx" - $CertificatePassword = ConvertTo-SecureString -String $Env:CODESIGN_P12_PASSWORD -Force -AsPlainText - } Else { - $CertificateFile = $null - $CertificatePassword = $null - } - & .\scripts\package\win-package-portable.ps1 -BuildNumber $Env:BUILD_NUMBER ` - -CertificateFile $CertificateFile -CertificatePassword $CertificatePassword Move-Item .\dist\*.exe .\artifacts - env: - CODESIGN_P12_PASSWORD: ${{ secrets.CODESIGN_P12_PASSWORD }} - - name: Cleanup - if: env.CODESIGN == '1' - run: Remove-Item .\codesign.pfx + - name: Build Windows 10 store app package + if: matrix.setup.type == 'store-app' + run: | + & .\scripts\package\win-package-appx.ps1 dist\picard + Move-Item .\dist\*.msix .\artifacts + - name: Sign final executable + uses: azure/trusted-signing-action@v0.5.0 + if: matrix.setup.type != 'store-app' && env.CODESIGN == 'true' + with: + azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} + azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} + azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} + endpoint: ${{ secrets.AZURE_ENDPOINT }} + trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }} + certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }} + files-folder: artifacts + files-folder-filter: exe + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 - name: Archive production artifacts uses: actions/upload-artifact@v4 - if: matrix.type != 'signed-app' || env.CODESIGN == '1' with: - name: windows-${{ matrix.type }} + name: windows-${{ matrix.setup.type }} path: artifacts/ diff --git a/scripts/package/win-build-fixes.ps1 b/scripts/package/win-build-fixes.ps1 new file mode 100644 index 0000000000..494ce6397f --- /dev/null +++ b/scripts/package/win-build-fixes.ps1 @@ -0,0 +1,18 @@ +# Apply fixes to the build result + +Param( + [ValidateScript({Test-Path $_ -PathType Container})] + [String] + $Path +) + +$ErrorActionPreference = 'Stop' + +# Move all Qt5 DLLs into the main folder to avoid conflicts with system wide +# versions of those dependencies. Since some version PyInstaller tries to +# maintain the file hierarchy of imported modules, but this easily breaks +# DLL loading on Windows. +# Workaround for https://tickets.metabrainz.org/browse/PICARD-2736 +$Qt5BinDir = (Join-Path -Path $Path -ChildPath PyQt5\Qt5\bin) +Move-Item -Path (Join-Path -Path $Qt5BinDir -ChildPath *.dll) -Destination $Path -Force +Remove-Item -Path $Qt5BinDir diff --git a/scripts/package/win-common.ps1 b/scripts/package/win-common.ps1 index 5e8c6d6e7d..e903b8c7c0 100644 --- a/scripts/package/win-common.ps1 +++ b/scripts/package/win-common.ps1 @@ -8,53 +8,6 @@ Param( $CertificatePassword ) -# RFC 3161 timestamp server for code signing -$TimeStampServer = 'http://ts.ssl.com' - -Function CodeSignBinary { - Param( - [ValidateScript({Test-Path $_ -PathType Leaf})] - [String] - $BinaryPath - ) - If ($CertificateFile) { - SignTool sign /v /fd SHA256 /tr "$TimeStampServer" /td sha256 ` - /f "$CertificateFile" /p (ConvertFrom-SecureString -AsPlainText $CertificatePassword) ` - $BinaryPath - ThrowOnExeError "SignTool failed" - } Else { - Write-Output "Skip signing $BinaryPath" - } -} - -Function ThrowOnExeError { - Param( [String]$Message ) - If ($LastExitCode -ne 0) { - Throw $Message - } -} - -Function FinalizePackage { - Param( - [ValidateScript({Test-Path $_ -PathType Container})] - [String] - $Path - ) - - CodeSignBinary -BinaryPath (Join-Path -Path $Path -ChildPath picard.exe) -ErrorAction Stop - CodeSignBinary -BinaryPath (Join-Path -Path $Path -ChildPath fpcalc.exe) -ErrorAction Stop - CodeSignBinary -BinaryPath (Join-Path -Path $Path -ChildPath discid.dll) -ErrorAction Stop - - # Move all Qt5 DLLs into the main folder to avoid conflicts with system wide - # versions of those dependencies. Since some version PyInstaller tries to - # maintain the file hierarchy of imported modules, but this easily breaks - # DLL loading on Windows. - # Workaround for https://tickets.metabrainz.org/browse/PICARD-2736 - $Qt5BinDir = (Join-Path -Path $Path -ChildPath PyQt5\Qt5\bin) - Move-Item -Path (Join-Path -Path $Qt5BinDir -ChildPath *.dll) -Destination $Path -Force - Remove-Item -Path $Qt5BinDir -} - Function DownloadFile { Param( [Parameter(Mandatory = $true)] diff --git a/scripts/package/win-package-appx.ps1 b/scripts/package/win-package-appx.ps1 index 50f41f8664..6941afc510 100644 --- a/scripts/package/win-package-appx.ps1 +++ b/scripts/package/win-package-appx.ps1 @@ -1,72 +1,24 @@ # Build a MSIX app package for Windows 10 Param( - [ValidateScript({ (Test-Path $_ -PathType Leaf) -or (-not $_) })] + [ValidateScript({ Test-Path $_ -PathType Container })] [String] - $CertificateFile, - [SecureString] - $CertificatePassword, - [Int] - $BuildNumber + $PackageDir ) -# Errors are handled explicitly. Otherwise any output to stderr when -# calling classic Windows exes causes a script error. -# TODO: For PowerShell >= 7.3 use $PSNativeCommandUseErrorActionPreference = $true -$ErrorActionPreference = 'Continue' +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true -If (-Not $BuildNumber) { - $BuildNumber = 0 -} - -If (-Not $Certificate -And $CertificateFile) { - $Certificate = Get-PfxCertificate -FilePath $CertificateFile -Password $CertificatePassword -} - -$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -. $ScriptDirectory\win-common.ps1 -CertificateFile $CertificateFile -CertificatePassword $CertificatePassword - -Write-Output "Building Windows 10 app package..." - -# Set the publisher based on the certificate subject -if ($Certificate) { - $env:PICARD_APPX_PUBLISHER = $Certificate.Subject - Write-Output "Publisher: $env:PICARD_APPX_PUBLISHER" -} - -# Build -Remove-Item -Path build,dist/picard,locale -Recurse -ErrorAction Ignore -python setup.py clean 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py clean failed" -python setup.py build --build-number=$BuildNumber --disable-autoupdate 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py build failed" -python setup.py build_ext -i 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py build_ext -i failed" - -# Package application -pyinstaller --noconfirm --clean picard.spec 2>&1 | %{ "$_" } -ThrowOnExeError "PyInstaller failed" -$PackageDir = (Resolve-Path dist\picard) -FinalizePackage $PackageDir +$PackageDir = (Resolve-Path $PackageDir) # Generate resource files Copy-Item appxmanifest.xml $PackageDir $PriConfigFile = (Join-Path (Resolve-Path .\build) priconfig.xml) Push-Location $PackageDir MakePri createconfig /ConfigXml $PriConfigFile /Default en-US /Overwrite -ThrowOnExeError "MakePri createconfig failed" MakePri new /ProjectRoot $PackageDir /ConfigXml $PriConfigFile -ThrowOnExeError "MakePri new failed" Pop-Location # Generate msix package -$PicardVersion = (python -c "import picard; print(picard.__version__)") -If ($CertificateFile -or $Certificate) { - $PackageFile = "dist\MusicBrainz-Picard-$PicardVersion.msix" -} Else { - $PackageFile = "dist\MusicBrainz-Picard-$PicardVersion-unsigned.msix" -} +$PackageFile = "dist\MusicBrainz-Picard-${PicardVersion}_unsigned.msix" MakeAppx pack /o /h SHA256 /d $PackageDir /p $PackageFile -ThrowOnExeError "MakeAppx failed" - -CodeSignBinary -BinaryPath $PackageFile -ErrorAction Stop diff --git a/scripts/package/win-package-installer.ps1 b/scripts/package/win-package-installer.ps1 deleted file mode 100644 index 4653973df1..0000000000 --- a/scripts/package/win-package-installer.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -# Build a Windows installer - -Param( - [ValidateScript({ (Test-Path $_ -PathType Leaf) -or (-not $_) })] - [String] - $CertificateFile, - [SecureString] - $CertificatePassword, - [Int] - $BuildNumber -) - -# Errors are handled explicitly. Otherwise any output to stderr when -# calling classic Windows exes causes a script error. -$ErrorActionPreference = 'Continue' - -If (-Not $BuildNumber) { - $BuildNumber = 0 -} - -$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -. $ScriptDirectory\win-common.ps1 -CertificateFile $CertificateFile -CertificatePassword $CertificatePassword - -Write-Output "Building Windows installer..." - -# Build -Remove-Item -Path build,dist/picard,locale -Recurse -ErrorAction Ignore -python setup.py clean 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py clean failed" -python setup.py build --build-number=$BuildNumber 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py build failed" -python setup.py build_ext -i 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py build_ext -i failed" - -# Package application -pyinstaller --noconfirm --clean picard.spec 2>&1 | %{ "$_" } -ThrowOnExeError "PyInstaller failed" -FinalizePackage dist\picard - -# Build the installer -makensis.exe /INPUTCHARSET UTF8 installer\picard-setup.nsi 2>&1 | %{ "$_" } -ThrowOnExeError "NSIS failed" -CodeSignBinary -BinaryPath installer\picard-setup-*.exe -ErrorAction Stop diff --git a/scripts/package/win-package-portable.ps1 b/scripts/package/win-package-portable.ps1 deleted file mode 100644 index 79b4633efc..0000000000 --- a/scripts/package/win-package-portable.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -# Build a portable app - -Param( - [ValidateScript({ (Test-Path $_ -PathType Leaf) -or (-not $_) })] - [String] - $CertificateFile, - [SecureString] - $CertificatePassword, - [Int] - $BuildNumber -) - -# Errors are handled explicitly. Otherwise any output to stderr when -# calling classic Windows exes causes a script error. -$ErrorActionPreference = 'Continue' - -If (-Not $BuildNumber) { - $BuildNumber = 0 -} - -$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -. $ScriptDirectory\win-common.ps1 -CertificateFile $CertificateFile -CertificatePassword $CertificatePassword - -Write-Output "Building portable exe..." - -# Build -Remove-Item -Path build,locale -Recurse -ErrorAction Ignore -python setup.py clean 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py clean failed" -python setup.py build --build-number=$BuildNumber 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py build failed" -python setup.py build_ext -i 2>&1 | %{ "$_" } -ThrowOnExeError "setup.py build_ext -i failed" - -# Package application -$env:PICARD_BUILD_PORTABLE = '1' -pyinstaller --noconfirm --clean picard.spec 2>&1 | %{ "$_" } -ThrowOnExeError "PyInstaller failed" -CodeSignBinary -BinaryPath dist\MusicBrainz-Picard-*.exe -ErrorAction Stop