From da7d5ce60827b43517e52aa604b9f2180a44548a Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 6 Feb 2025 10:43:23 +0100 Subject: [PATCH] build: switch to cloud code signing for Windows (#9948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The requirements for Windows code signing changed in 2023, so that newly generated certificates can only be stored in hardware modules. Luckily, I managed to snag a three year certificate before that so it hasn't affected us so much. Now though, it does, because our cert is expiring in March. This changes the code signing process for Windows to use a cloud service, Azure Trusted Signing. This appears to work equally well and outsources the problem entirely, while also being cheaper than the actual certificate was to begin with. 🤷 The signing entity will be Kastelo AB and not the Syncthing Foundation, because the latter is almost impossible to get a certificate for as it's not a normal corporate entity whose existence can be verified, etc. This is also how it was prior to the latest certificate; it's not ideal, but I think it's acceptable under the circumstances. --- .github/workflows/build-syncthing.yaml | 64 ++++++++++++++++++++--- build.go | 70 +------------------------- 2 files changed, 57 insertions(+), 77 deletions(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index dd8b12640..fb8d94087 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -127,6 +127,7 @@ jobs: - package-cross - package-source - package-debian + - package-windows - govulncheck steps: - uses: actions/checkout@v4 @@ -137,8 +138,6 @@ jobs: package-windows: name: Package for Windows - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) - environment: release runs-on: windows-latest steps: - name: Set git to use LF @@ -188,16 +187,65 @@ jobs: } env: CGO_ENABLED: "0" - CODESIGN_SIGNTOOL: ${{ secrets.CODESIGN_SIGNTOOL }} - CODESIGN_CERTIFICATE_BASE64: ${{ secrets.CODESIGN_CERTIFICATE_BASE64 }} - CODESIGN_CERTIFICATE_PASSWORD: ${{ secrets.CODESIGN_CERTIFICATE_PASSWORD }} - CODESIGN_TIMESTAMP_SERVER: ${{ secrets.CODESIGN_TIMESTAMP_SERVER }} + + - name: Archive artifacts + uses: actions/upload-artifact@v4 + with: + name: unsigned-packages-windows + path: "*.zip" + + codesign-windows: + name: Codesign for Windows + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) + environment: release + runs-on: windows-latest + needs: + - package-windows + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: unsigned-packages-windows + path: packages + + - name: Extract packages + working-directory: packages + run: | + $files = Get-ChildItem "." -Filter *.zip + foreach ($file in $files) { + 7z x $file.Name + } + + - name: Sign files with Trusted Signing + uses: azure/trusted-signing-action@v0.5.1 + with: + azure-tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }} + azure-client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }} + azure-client-secret: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_SECRET }} + endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }} + trusted-signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT }} + certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_PROFILE }} + files-folder: ${{ github.workspace }}\packages + files-folder-filter: exe + files-folder-recurse: true + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + + - name: Repackage packages + working-directory: packages + run: | + $files = Get-ChildItem "." -Filter *.zip + foreach ($file in $files) { + Remove-Item $file.Name + 7z a -tzip $file.Name $file.BaseName + } - name: Archive artifacts uses: actions/upload-artifact@v4 with: name: packages-windows - path: "*.zip" + path: "packages/*.zip" # # Linux @@ -502,7 +550,7 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) environment: release needs: - - package-windows + - codesign-windows - package-linux - package-macos - package-cross diff --git a/build.go b/build.go index f7c106313..08d81414e 100644 --- a/build.go +++ b/build.go @@ -15,7 +15,6 @@ import ( "bytes" "compress/flate" "compress/gzip" - "encoding/base64" "encoding/json" "errors" "flag" @@ -1345,10 +1344,7 @@ func zipFile(out string, files []archiveFile) { } func codesign(target target) { - switch goos { - case "windows": - windowsCodesign(target.BinaryName()) - case "darwin": + if goos == "darwin" { macosCodesign(target.BinaryName()) } } @@ -1372,70 +1368,6 @@ func macosCodesign(file string) { } } -func windowsCodesign(file string) { - st := "signtool.exe" - - if path := os.Getenv("CODESIGN_SIGNTOOL"); path != "" { - st = path - } - - for i, algo := range []string{"sha1", "sha256"} { - args := []string{"sign", "/fd", algo} - if f := os.Getenv("CODESIGN_CERTIFICATE_FILE"); f != "" { - args = append(args, "/f", f) - } else if b := os.Getenv("CODESIGN_CERTIFICATE_BASE64"); b != "" { - // Decode the PFX certificate from base64. - bs, err := base64.RawStdEncoding.DecodeString(b) - if err != nil { - log.Println("Codesign: signing failed: decoding base64:", err) - return - } - - // Write it to a temporary file - f, err := os.CreateTemp("", "codesign-*.pfx") - if err != nil { - log.Println("Codesign: signing failed: creating temp file:", err) - return - } - _ = f.Chmod(0o600) // best effort remove other users' access - defer os.Remove(f.Name()) - if _, err := f.Write(bs); err != nil { - log.Println("Codesign: signing failed: writing temp file:", err) - return - } - if err := f.Close(); err != nil { - log.Println("Codesign: signing failed: closing temp file:", err) - return - } - - // Use that when signing - args = append(args, "/f", f.Name()) - } - if p := os.Getenv("CODESIGN_CERTIFICATE_PASSWORD"); p != "" { - args = append(args, "/p", p) - } - if tr := os.Getenv("CODESIGN_TIMESTAMP_SERVER"); tr != "" { - switch algo { - case "sha256": - args = append(args, "/tr", tr, "/td", algo) - default: - args = append(args, "/t", tr) - } - } - if i > 0 { - args = append(args, "/as") - } - args = append(args, file) - - bs, err := runError(st, args...) - if err != nil { - log.Printf("Codesign: signing failed: %v: %s", err, string(bs)) - return - } - log.Println("Codesign: successfully signed", file, "using", algo) - } -} - func metalint() { lazyRebuildAssets() runPrint(goCmd, "test", "-run", "Metalint", "./meta")