mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-18 21:20:57 +00:00
Sign Windows release builds with the SignPath release certificate
Release-branch builds sign every unsigned executable, DLL and Python module in the x64 and arm64 portable bundles and installers, after approval in SignPath. The binaries and installer now carry OrcaSlicer version information.
This commit is contained in:
1 parent
40761ec6e2
commit
948e6637fb
12 files changed
+298
-116
No files matched your search
@@ -424,6 +424,10 @@ jobs:
|
||||
"BUILD_DIR=build" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||
"ARCH_SUFFIX=_x64" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||
}
|
||||
# Release branches ship binaries and installers signed through SignPath with the
|
||||
# release certificate. Every signing request waits for a manual approval in
|
||||
# SignPath, so main and pull request builds stay unsigned.
|
||||
"SIGN_WINDOWS=${{ github.repository == 'OrcaSlicer/OrcaSlicer' && startsWith(github.ref, 'refs/heads/release/') && !vars.SELF_HOSTED }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||
|
||||
- name: setup MSVC
|
||||
if: runner.os == 'Windows'
|
||||
@@ -504,100 +508,117 @@ jobs:
|
||||
retention-days: 5
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload unsigned Windows portable artifact for SignPath
|
||||
id: upload-windows-portable
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
# Signs OrcaSlicer's own binaries and every bundled executable, DLL and Python
|
||||
# extension module that ships without a signature; files their vendor signed, such
|
||||
# as the Microsoft runtime, keep that signature. See signpath/README.md.
|
||||
- name: Stage Windows binaries for SignPath
|
||||
id: stage-windows-binaries
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = "${{ env.ver }}" -replace '^V', ''
|
||||
./scripts/stage-for-signpath.ps1 -Bundle "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer" -Destination "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-binaries" -ProductVersion $version
|
||||
"version=$version" | Out-File -Append -FilePath $env:GITHUB_OUTPUT -Encoding utf8
|
||||
|
||||
- name: Upload unsigned Windows binaries for SignPath
|
||||
id: upload-windows-binaries
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable_unsigned
|
||||
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer
|
||||
name: signpath_windows_binaries${{ env.ARCH_SUFFIX }}_unsigned
|
||||
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-binaries
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Submit Windows portable artifact to SignPath
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
# Waits until the request is approved in SignPath.
|
||||
- name: Sign Windows binaries with SignPath
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: OrcaSlicer
|
||||
signing-policy-slug: test-signing
|
||||
artifact-configuration-slug: windows-portable-v1
|
||||
github-artifact-id: ${{ steps.upload-windows-portable.outputs.artifact-id }}
|
||||
signing-policy-slug: release-signing
|
||||
artifact-configuration-slug: windows-binaries-v1
|
||||
github-artifact-id: ${{ steps.upload-windows-binaries.outputs.artifact-id }}
|
||||
parameters: |
|
||||
version: "${{ steps.stage-windows-binaries.outputs.version }}"
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/windows-portable
|
||||
wait-for-completion-timeout-in-seconds: 10800
|
||||
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries
|
||||
|
||||
- name: Verify SignPath Windows portable signatures
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
- name: Verify Windows binary signatures
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
# -AllowUntrustedRoot is required while signing with the SignPath test
|
||||
# certificate (self-signed). Remove it once signing-policy-slug switches
|
||||
# to release-signing with a production CA-issued certificate.
|
||||
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/windows-portable' -AllowUntrustedRoot
|
||||
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries'
|
||||
|
||||
- name: Replace Windows binaries with signed output
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$source = Join-Path "${{ github.workspace }}" "${{ env.BUILD_DIR }}/signpath/windows-portable"
|
||||
$destination = Join-Path "${{ github.workspace }}" "${{ env.BUILD_DIR }}/OrcaSlicer"
|
||||
if (-not (Test-Path -LiteralPath $source -PathType Container)) {
|
||||
throw "SignPath output directory not found: $source"
|
||||
}
|
||||
Get-ChildItem -LiteralPath $source -Force | Copy-Item -Destination $destination -Recurse -Force
|
||||
|
||||
# cpack's install rules take the binaries from the build output, not from
|
||||
# the portable bundle, so the installer needs the signed copies there too.
|
||||
$buildOutput = Join-Path "${{ github.workspace }}" "${{ env.BUILD_DIR }}/src/Release"
|
||||
if (-not (Test-Path -LiteralPath $buildOutput -PathType Container)) {
|
||||
throw "Build output directory not found: $buildOutput"
|
||||
}
|
||||
foreach ($name in "orca-slicer.exe", "OrcaSlicer.dll") {
|
||||
Copy-Item -LiteralPath (Join-Path $source $name) -Destination $buildOutput -Force
|
||||
}
|
||||
run: ./scripts/restore-from-signpath.ps1 -SignedDirectory "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries" -Bundle "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer"
|
||||
|
||||
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's
|
||||
# x86 emulation, packaging the native arm64 payload from build-arm64.
|
||||
# The installer packs the installed OrcaSlicer/ tree, which the portable zip and the
|
||||
# MSIX are made from too, instead of re-running the install rules: those take
|
||||
# CPython and uv from the dependency tree and the download cache, which keep the
|
||||
# unsigned copies.
|
||||
- name: Create installer Win
|
||||
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cpack -G NSIS
|
||||
$bundle = "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer".Replace('\', '/')
|
||||
cpack -G NSIS -D CPACK_INSTALL_CMAKE_PROJECTS= -D "CPACK_INSTALLED_DIRECTORIES=$bundle;."
|
||||
|
||||
# cpack stages what the installer packs under _CPack_Packages/<system>/NSIS/<package>;
|
||||
# every executable and DLL there must carry a valid signature, ours or its vendor's.
|
||||
- name: Verify every binary the installer packs is signed
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$payload = Get-Item "${{ github.workspace }}/${{ env.BUILD_DIR }}/_CPack_Packages/*/NSIS/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}"
|
||||
./scripts/verify-authenticode.ps1 -ArtifactDirectory $payload.FullName
|
||||
|
||||
- name: Stage Windows installer for SignPath
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
run: ./scripts/stage-for-signpath.ps1 -Path "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe" -Destination "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-installer" -ProductVersion "${{ steps.stage-windows-binaries.outputs.version }}"
|
||||
|
||||
- name: Upload unsigned Windows installer for SignPath
|
||||
id: upload-windows-installer
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}_unsigned
|
||||
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe
|
||||
name: signpath_windows_installer${{ env.ARCH_SUFFIX }}_unsigned
|
||||
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-installer
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Submit Windows installer to SignPath
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
- name: Sign Windows installer with SignPath
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: OrcaSlicer
|
||||
signing-policy-slug: test-signing
|
||||
signing-policy-slug: release-signing
|
||||
artifact-configuration-slug: windows-installer-v1
|
||||
github-artifact-id: ${{ steps.upload-windows-installer.outputs.artifact-id }}
|
||||
parameters: |
|
||||
version: "${{ steps.stage-windows-binaries.outputs.version }}"
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/windows-installer
|
||||
wait-for-completion-timeout-in-seconds: 3600
|
||||
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer
|
||||
|
||||
- name: Verify SignPath Windows installer signature
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
- name: Verify Windows installer signature
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
# -AllowUntrustedRoot: see "Verify SignPath Windows portable signatures".
|
||||
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/windows-installer' -Files 'OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe' -AllowUntrustedRoot
|
||||
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer' -Files 'OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe'
|
||||
|
||||
- name: Replace Windows installer with signed output
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
if: env.SIGN_WINDOWS == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$name = "OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe"
|
||||
$source = Join-Path "${{ github.workspace }}" "${{ env.BUILD_DIR }}/signpath/windows-installer/$name"
|
||||
Copy-Item -LiteralPath $source -Destination (Join-Path "${{ github.workspace }}" "${{ env.BUILD_DIR }}/$name") -Force
|
||||
run: Copy-Item -LiteralPath "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe" -Destination "${{ github.workspace }}/${{ env.BUILD_DIR }}" -Force
|
||||
|
||||
- name: Pack app
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
@@ -1355,6 +1355,18 @@ set (CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer")
|
||||
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command
|
||||
# Version information of the installer, the same as the binaries it installs carry
|
||||
# (src/dev-utils/platform/msw/OrcaSlicer-version.rc.in): SignPath only signs it when
|
||||
# ProductName and ProductVersion match signpath/windows-installer-v1.xml. The NSIS strings
|
||||
# are single-quoted because CPack writes this value into CPackConfig.cmake unescaped.
|
||||
string (CONCAT CPACK_NSIS_DEFINES
|
||||
"VIProductVersion '${ORCA_VERSION_MAJOR}.${ORCA_VERSION_MINOR}.${ORCA_VERSION_PATCH}.0'\n"
|
||||
"VIFileVersion '${ORCA_VERSION_MAJOR}.${ORCA_VERSION_MINOR}.${ORCA_VERSION_PATCH}.0'\n"
|
||||
"VIAddVersionKey /LANG=1033 'CompanyName' 'SoftFever'\n"
|
||||
"VIAddVersionKey /LANG=1033 'FileDescription' '${SLIC3R_APP_NAME} Installer'\n"
|
||||
"VIAddVersionKey /LANG=1033 'FileVersion' '${SoftFever_VERSION}'\n"
|
||||
"VIAddVersionKey /LANG=1033 'ProductName' '${SLIC3R_APP_NAME}'\n"
|
||||
"VIAddVersionKey /LANG=1033 'ProductVersion' '${SoftFever_VERSION}'\n")
|
||||
|
||||
set(CPACK_WIX_UPGRADE_GUID "058245e8-20e0-4a95-9ab7-1acfe17ad511")
|
||||
set(CPACK_GENERATOR NSIS)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copies the files SignPath signed back over the originals in the portable bundle they
|
||||
# were staged from with stage-for-signpath.ps1 -Bundle: the root of the signed artifact
|
||||
# holds OrcaSlicer's own binaries, third-party/ the other files under their paths in the
|
||||
# bundle.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||
[string]$SignedDirectory,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||
[string]$Bundle
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$signedRoot = (Resolve-Path -LiteralPath $SignedDirectory).Path.TrimEnd('\', '/')
|
||||
$thirdPartyPrefix = "third-party" + [IO.Path]::DirectorySeparatorChar
|
||||
$files = Get-ChildItem -LiteralPath $signedRoot -Recurse -File
|
||||
if (-not $files) {
|
||||
throw "No signed files found in '$signedRoot'."
|
||||
}
|
||||
|
||||
foreach ($file in $files) {
|
||||
$relative = $file.FullName.Substring($signedRoot.Length + 1)
|
||||
if ($relative.StartsWith($thirdPartyPrefix, [StringComparison]::OrdinalIgnoreCase)) {
|
||||
$relative = $relative.Substring($thirdPartyPrefix.Length)
|
||||
}
|
||||
|
||||
# Every signed file replaces one that was staged, so a file without an original means
|
||||
# the artifact does not have the layout stage-for-signpath.ps1 produces.
|
||||
$target = Join-Path $Bundle $relative
|
||||
if (-not (Test-Path -LiteralPath $target -PathType Leaf)) {
|
||||
throw "Signed file '$relative' has no original in '$Bundle'."
|
||||
}
|
||||
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
|
||||
}
|
||||
|
||||
Write-Host "Restored $($files.Count) signed files into '$Bundle'."
|
||||
@@ -0,0 +1,81 @@
|
||||
# Copies the files a SignPath signing request covers into the directory that request
|
||||
# uploads, after checking their version information. SignPath refuses to sign a file
|
||||
# whose ProductName or ProductVersion differs from its artifact configuration (see
|
||||
# signpath/), and reports it only once the request has been approved, so the check runs
|
||||
# before anything is submitted.
|
||||
#
|
||||
# -Path stages the given files at the root of -Destination.
|
||||
#
|
||||
# -Bundle stages a portable OrcaSlicer bundle for signpath/windows-binaries-v1.xml:
|
||||
# OrcaSlicer's own binaries (-OwnBinaries) at the root, where their version information
|
||||
# is checked, and every other executable, DLL and Python extension module that carries no
|
||||
# signature under third-party/, at its path in the bundle. Files signed by their vendor,
|
||||
# such as the Microsoft runtime, keep that signature. restore-from-signpath.ps1 copies the
|
||||
# signed files back.
|
||||
[CmdletBinding(DefaultParameterSetName = "Path")]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, ParameterSetName = "Path")]
|
||||
[string[]]$Path,
|
||||
|
||||
[Parameter(Mandatory = $true, ParameterSetName = "Bundle")]
|
||||
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||
[string]$Bundle,
|
||||
|
||||
[Parameter(ParameterSetName = "Bundle")]
|
||||
[string[]]$OwnBinaries = @("orca-slicer.exe", "OrcaSlicer.dll"),
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Destination,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ProductVersion,
|
||||
|
||||
[string]$ProductName = "OrcaSlicer"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Test-VersionInfo {
|
||||
param([string]$File)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $File -PathType Leaf)) {
|
||||
throw "File to sign was not found: $File"
|
||||
}
|
||||
|
||||
$info = [Diagnostics.FileVersionInfo]::GetVersionInfo((Resolve-Path -LiteralPath $File).Path)
|
||||
if ($info.ProductName -ne $ProductName -or $info.ProductVersion -ne $ProductVersion) {
|
||||
throw "'$File' has ProductName '$($info.ProductName)' and ProductVersion '$($info.ProductVersion)'; SignPath expects '$ProductName' and '$ProductVersion'."
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
|
||||
|
||||
if ($PSCmdlet.ParameterSetName -eq "Path") {
|
||||
foreach ($file in $Path) {
|
||||
Test-VersionInfo $file
|
||||
Copy-Item -LiteralPath $file -Destination $Destination -Force
|
||||
Write-Host "Staged $(Split-Path -Leaf $file) ($ProductName $ProductVersion)"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
$bundleRoot = (Resolve-Path -LiteralPath $Bundle).Path.TrimEnd('\', '/')
|
||||
foreach ($name in $OwnBinaries) {
|
||||
$file = Join-Path $bundleRoot $name
|
||||
Test-VersionInfo $file
|
||||
Copy-Item -LiteralPath $file -Destination $Destination -Force
|
||||
Write-Host "Staged $name ($ProductName $ProductVersion)"
|
||||
}
|
||||
|
||||
$thirdParty = Get-ChildItem -LiteralPath $bundleRoot -Recurse -File |
|
||||
Where-Object { $_.Extension -in ".exe", ".dll", ".pyd" } |
|
||||
Where-Object { $_.DirectoryName -ne $bundleRoot -or $_.Name -notin $OwnBinaries } |
|
||||
Where-Object { (Get-AuthenticodeSignature -LiteralPath $_.FullName).Status -eq "NotSigned" }
|
||||
|
||||
foreach ($file in $thirdParty) {
|
||||
$relative = $file.FullName.Substring($bundleRoot.Length + 1)
|
||||
$target = Join-Path (Join-Path $Destination "third-party") $relative
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $target) | Out-Null
|
||||
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
|
||||
}
|
||||
Write-Host "Staged $(@($thirdParty).Count) unsigned third-party files under third-party/"
|
||||
@@ -4,17 +4,15 @@ param(
|
||||
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||
[string]$ArtifactDirectory,
|
||||
|
||||
[string[]]$Files = @(
|
||||
"orca-slicer.exe",
|
||||
"OrcaSlicer.dll"
|
||||
),
|
||||
# Paths relative to -ArtifactDirectory. Without them every executable, DLL and Python
|
||||
# extension module under it is verified.
|
||||
[string[]]$Files,
|
||||
|
||||
[string]$SignToolPath,
|
||||
|
||||
# Accept signatures whose certificate chain terminates in an untrusted root.
|
||||
# Required for the SignPath test certificate (self-signed). Do NOT pass this
|
||||
# once a production CA-issued certificate is in use, so release builds enforce
|
||||
# a fully trusted chain.
|
||||
# with the release certificate, so release builds enforce a fully trusted chain.
|
||||
[switch]$AllowUntrustedRoot
|
||||
)
|
||||
|
||||
@@ -57,11 +55,20 @@ function Resolve-SignToolPath {
|
||||
throw "signtool.exe was not found. Install the Windows SDK or pass -SignToolPath."
|
||||
}
|
||||
|
||||
$artifactRoot = (Resolve-Path -LiteralPath $ArtifactDirectory).Path
|
||||
$artifactRoot = (Resolve-Path -LiteralPath $ArtifactDirectory).Path.TrimEnd('\', '/')
|
||||
$signtool = Resolve-SignToolPath -ExplicitPath $SignToolPath
|
||||
|
||||
if (-not $Files) {
|
||||
$Files = Get-ChildItem -LiteralPath $artifactRoot -Recurse -File |
|
||||
Where-Object { $_.Extension -in ".exe", ".dll", ".pyd" } |
|
||||
ForEach-Object { $_.FullName.Substring($artifactRoot.Length + 1) }
|
||||
if (-not $Files) {
|
||||
throw "No executables or DLLs found in '$artifactRoot'."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Using SignTool: $signtool"
|
||||
Write-Host "Verifying Authenticode signatures in: $artifactRoot"
|
||||
Write-Host "Verifying Authenticode signatures of $(@($Files).Count) files in: $artifactRoot"
|
||||
|
||||
foreach ($relativePath in $Files) {
|
||||
$filePath = Join-Path $artifactRoot $relativePath
|
||||
@@ -69,8 +76,6 @@ foreach ($relativePath in $Files) {
|
||||
throw "Expected signed file was not found: $filePath"
|
||||
}
|
||||
|
||||
Write-Host "Verifying $relativePath"
|
||||
|
||||
# Capture signtool output without letting native stderr (redirected via 2>&1)
|
||||
# raise a terminating NativeCommandError under $ErrorActionPreference = 'Stop'.
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
@@ -83,9 +88,9 @@ foreach ($relativePath in $Files) {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
|
||||
$output | ForEach-Object { Write-Host $_ }
|
||||
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $filePath
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Host "Verified $relativePath ($($signature.SignerCertificate.Subject))"
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -95,15 +100,16 @@ foreach ($relativePath in $Files) {
|
||||
# structured Get-AuthenticodeSignature result instead: accept only when the
|
||||
# file is genuinely signed and the sole problem is that the chain terminates
|
||||
# in an untrusted root (i.e. the self-signed SignPath test certificate).
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $filePath
|
||||
$isSigned = ($signature.SignatureType -eq "Authenticode") -and ($null -ne $signature.SignerCertificate)
|
||||
$untrustedRootOnly = $signature.StatusMessage -match "terminated in a root certificate which is not trusted"
|
||||
if ($isSigned -and $untrustedRootOnly) {
|
||||
Write-Host " Accepted: '$relativePath' is signed but its certificate chains to an untrusted root (expected for the SignPath test certificate)."
|
||||
Write-Host "Accepted $relativePath ($($signature.SignerCertificate.Subject)): signed, but its certificate chains to an untrusted root (expected for the SignPath test certificate)."
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
# The full chain and error details only for a file that did not verify.
|
||||
$output | ForEach-Object { Write-Host " $_" }
|
||||
throw "SignTool verification failed for '$relativePath' with exit code $exitCode."
|
||||
}
|
||||
|
||||
|
||||
+10
-11
@@ -1,22 +1,21 @@
|
||||
# SignPath configurations
|
||||
|
||||
This directory contains SignPath artifact configurations used by GitHub Actions.
|
||||
Windows builds of `release/*` branches in `OrcaSlicer/OrcaSlicer` are code signed through [SignPath](https://about.signpath.io/), with the certificate the SignPath Foundation issues to open source projects. The XML files here are copies of the project's artifact configurations in SignPath, which the workflow refers to by slug only; a change to one has to be made in SignPath too, under the same slug.
|
||||
|
||||
## `windows-portable-v1`
|
||||
Every signing request uses the `release-signing` policy and waits in SignPath until it is approved, so each Windows build of a release branch needs two approvals per architecture: the binaries, then the installer. Builds of `main` and pull requests are not signed.
|
||||
|
||||
`windows-portable-v1.xml` is the initial conservative Windows portable-bundle signing configuration. It signs only the two first-party binaries:
|
||||
## What gets signed
|
||||
|
||||
- `orca-slicer.exe`
|
||||
- `OrcaSlicer.dll`
|
||||
Every executable, DLL and Python extension module in the bundle ends up with a valid signature, so Windows features that check each loaded binary, such as Smart App Control, find none unsigned. Files their vendor already signed keep that signature: the Microsoft runtime DLLs and `WebView2Loader.dll` (Microsoft) and CPython's bundled OpenSSL and libffi (Python Software Foundation). Everything else is signed with the project's certificate: OrcaSlicer's own `orca-slicer.exe` and `OrcaSlicer.dll`, the libraries built from upstream sources (OCCT, FFmpeg, FreeType, GMP, MPFR, CPython), the downloaded `uv.exe`, and the NSIS installer.
|
||||
|
||||
Do not broaden this to all DLLs without first confirming ownership, provenance, and whether upstream vendor signatures should be verified instead.
|
||||
The SignPath Foundation's conditions ask projects to sign only binaries built from their own source and to leave upstream projects' binaries unsigned, which they allow inside a signed installer. Signing the upstream libraries departs from that, by the maintainers' choice. Narrowing the signed set back to OrcaSlicer's own files means dropping the `third-party` file set from `windows-binaries-v1` and the third-party staging from `scripts/stage-for-signpath.ps1`.
|
||||
|
||||
The Windows workflow uploads `${{ env.BUILD_DIR }}/OrcaSlicer` with `actions/upload-artifact`. GitHub stores that artifact as a ZIP, and the uploaded directory contents are rooted at the ZIP root. Because of that, the SignPath configuration uses `<zip-file>` with `orca-slicer.exe` and `OrcaSlicer.dll` directly beneath it.
|
||||
The Foundation also requires metadata restrictions on signed files. OrcaSlicer's own binaries and the installer must have the ProductName `OrcaSlicer` and the ProductVersion passed in with the request, which the workflow takes from `SoftFever_VERSION` in `version.inc`. The binaries get that version information from `src/dev-utils/platform/msw/OrcaSlicer-version.rc.in`, the installer from `CPACK_NSIS_DEFINES` in `CMakeLists.txt`. The upstream files carry their own projects' metadata, so their file set has no restriction. `scripts/stage-for-signpath.ps1` checks the version information before a request is submitted, since SignPath only rejects a mismatch after the request has been approved.
|
||||
|
||||
The release portable ZIP is a separate archive created with 7-Zip from `${{ env.BUILD_DIR }}/OrcaSlicer`; that archive keeps the top-level `OrcaSlicer/` folder. After SignPath returns the signed artifact, the workflow copies the signed files back into `${{ env.BUILD_DIR }}/OrcaSlicer` and recreates the portable release ZIP so the public ZIP layout stays unchanged. It also copies them over the build output in `${{ env.BUILD_DIR }}/src/Release`, because `cpack` packages the installer from there rather than from the portable bundle.
|
||||
## `windows-binaries-v1`
|
||||
|
||||
Signs the binaries of the installed bundle, `${{ env.BUILD_DIR }}/OrcaSlicer`. `scripts/stage-for-signpath.ps1 -Bundle` stages them for upload: `orca-slicer.exe` and `OrcaSlicer.dll` at the root of the artifact ZIP, where the metadata restrictions apply, and every other unsigned `.exe`, `.dll` and `.pyd` under `third-party/`, at its path in the bundle. `scripts/restore-from-signpath.ps1` copies the signed files back over the originals. The portable ZIP, the MSIX package and the installer are all packed from that bundle; `cpack` is pointed at it with `CPACK_INSTALLED_DIRECTORIES` rather than re-running the install rules, which would take CPython and `uv.exe` from their unsigned sources again. A check after `cpack` verifies that every binary in the installer's payload is signed.
|
||||
|
||||
## `windows-installer-v1`
|
||||
|
||||
`windows-installer-v1.xml` signs the NSIS installer that `cpack` produces, `OrcaSlicer_Windows_Installer_<version>_<arch>.exe`. The workflow uploads that single file, so it sits at the root of the artifact ZIP. The installer is built after the binaries are signed, so the files it installs carry the `windows-portable-v1` signatures.
|
||||
|
||||
Both configurations must also be created in the SignPath project under the same slugs; the workflow refers to them only by slug.
|
||||
Signs the NSIS installer `cpack` produces, `OrcaSlicer_Windows_Installer_<version>_<arch>.exe`, uploaded as the only file of its artifact. The uninstaller NSIS writes during installation is not signed.
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||
<parameters>
|
||||
<parameter name="version" required="true" />
|
||||
</parameters>
|
||||
<zip-file>
|
||||
<pe-file-set product-name="OrcaSlicer" product-version="${version}">
|
||||
<include path="orca-slicer.exe" />
|
||||
<include path="OrcaSlicer.dll" />
|
||||
<for-each>
|
||||
<authenticode-sign />
|
||||
</for-each>
|
||||
</pe-file-set>
|
||||
<directory path="third-party">
|
||||
<pe-file-set>
|
||||
<include path="**/*.exe" min-matches="0" max-matches="unbounded" />
|
||||
<include path="**/*.dll" min-matches="0" max-matches="unbounded" />
|
||||
<include path="**/*.pyd" min-matches="0" max-matches="unbounded" />
|
||||
<for-each>
|
||||
<authenticode-sign />
|
||||
</for-each>
|
||||
</pe-file-set>
|
||||
</directory>
|
||||
</zip-file>
|
||||
</artifact-configuration>
|
||||
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||
<parameters>
|
||||
<parameter name="version" required="true" />
|
||||
</parameters>
|
||||
<zip-file>
|
||||
<pe-file path="OrcaSlicer_Windows_Installer_*.exe">
|
||||
<pe-file path="OrcaSlicer_Windows_Installer_*.exe" product-name="OrcaSlicer" product-version="${version}">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
</zip-file>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||
<zip-file>
|
||||
<pe-file path="orca-slicer.exe">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
|
||||
<pe-file path="OrcaSlicer.dll">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
</zip-file>
|
||||
</artifact-configuration>
|
||||
+6
-2
@@ -140,7 +140,11 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.rc.
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.manifest @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
|
||||
if (WIN32)
|
||||
add_library(OrcaSlicer SHARED OrcaSlicer.cpp OrcaSlicer.hpp)
|
||||
foreach (_orca_rc_binary IN ITEMS orca-slicer.exe OrcaSlicer.dll)
|
||||
set(ORCA_RC_ORIGINAL_FILENAME ${_orca_rc_binary})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer-version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/${_orca_rc_binary}.version.rc @ONLY)
|
||||
endforeach ()
|
||||
add_library(OrcaSlicer SHARED OrcaSlicer.cpp OrcaSlicer.hpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.dll.version.rc)
|
||||
else ()
|
||||
add_executable(OrcaSlicer OrcaSlicer.cpp OrcaSlicer.hpp)
|
||||
endif ()
|
||||
@@ -216,7 +220,7 @@ if (WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
|
||||
endif()
|
||||
|
||||
add_executable(OrcaSlicer_app_gui WIN32 OrcaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.rc)
|
||||
add_executable(OrcaSlicer_app_gui WIN32 OrcaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.rc ${CMAKE_CURRENT_BINARY_DIR}/orca-slicer.exe.version.rc)
|
||||
# Generate debug symbols even in release mode.
|
||||
if(MSVC)
|
||||
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Version information of orca-slicer.exe and OrcaSlicer.dll, configured once per binary.
|
||||
// SignPath only signs a binary whose ProductName and ProductVersion match its artifact
|
||||
// configuration (signpath/windows-binaries-v1.xml), which the workflow passes the
|
||||
// SoftFever_VERSION from version.inc.
|
||||
1 VERSIONINFO
|
||||
FILEVERSION @ORCA_VERSION_MAJOR@,@ORCA_VERSION_MINOR@,@ORCA_VERSION_PATCH@,0
|
||||
PRODUCTVERSION @ORCA_VERSION_MAJOR@,@ORCA_VERSION_MINOR@,@ORCA_VERSION_PATCH@,0
|
||||
{
|
||||
BLOCK "StringFileInfo"
|
||||
{
|
||||
BLOCK "040904E4"
|
||||
{
|
||||
VALUE "CompanyName", "SoftFever"
|
||||
VALUE "FileDescription", "@SLIC3R_APP_NAME@"
|
||||
VALUE "FileVersion", "@SoftFever_VERSION@"
|
||||
VALUE "ProductName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "ProductVersion", "@SoftFever_VERSION@"
|
||||
VALUE "InternalName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "OriginalFilename", "@ORCA_RC_ORIGINAL_FILENAME@"
|
||||
}
|
||||
}
|
||||
BLOCK "VarFileInfo"
|
||||
{
|
||||
VALUE "Translation", 0x409, 1252
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,2 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION @SLIC3R_VERSION@
|
||||
PRODUCTVERSION @SLIC3R_VERSION@
|
||||
{
|
||||
BLOCK "StringFileInfo"
|
||||
{
|
||||
BLOCK "040904E4"
|
||||
{
|
||||
VALUE "CompanyName", "SoftFever"
|
||||
VALUE "FileDescription", "@SLIC3R_APP_NAME@"
|
||||
VALUE "FileVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "ProductName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "ProductVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "InternalName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "LegalCopyright", ""
|
||||
VALUE "OriginalFilename", "orca-slicer.exe"
|
||||
}
|
||||
}
|
||||
BLOCK "VarFileInfo"
|
||||
{
|
||||
VALUE "Translation", 0x409, 1252
|
||||
}
|
||||
}
|
||||
2 ICON "@SLIC3R_RESOURCES_DIR@/images/OrcaSlicer.ico"
|
||||
1 24 "OrcaSlicer.manifest"
|
||||
1 24 "OrcaSlicer.manifest"
|
||||
Reference in new issue
Block a user