Compare commits

...
Author SHA1 Message Date
SoftFever 948e6637fb 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.
2026-10-19 00:32:44 +08:00
SoftFever 40761ec6e2 Sign the Windows installer and the binaries it installs 2026-10-18 23:35:16 +08:00
SoftFever 186cd8e65f ci(windows): detect untrusted root via structured signature status
The previous text match on signtool output was flaky: signtool interleaves
stdout (chain details) and stderr (error), so the wrapped "untrusted root"
phrase stayed contiguous for one file but was split for another, causing the
verify step to pass orca-slicer.exe but throw on OrcaSlicer.dll.

Use Get-AuthenticodeSignature instead, whose StatusMessage is a single clean
string, to decide the untrusted-root exception. signtool remains the strict
primary check; this only governs the -AllowUntrustedRoot test-cert path.
2026-10-18 23:35:16 +08:00
SoftFever 8bc0eec4aa ci(windows): reset exit code after accepting untrusted test-cert root
The verify script accepted both signed binaries and printed "verification
passed", but the step still failed: signtool exits 1 on the untrusted test
root, and GitHub's pwsh wrapper exits with that lingering $LASTEXITCODE.
Add an explicit `exit 0` on the success path so the step passes.
2026-10-18 23:35:16 +08:00
SoftFever ec30dad1cf ci(windows): tolerate untrusted root in signature verification for test cert
signtool verify /pa exits non-zero when the certificate chain terminates in
an untrusted root, which is always the case for the SignPath self-signed test
certificate. Add an -AllowUntrustedRoot switch to verify-authenticode.ps1 that
accepts a signed-but-untrusted-root result (while still failing on unsigned or
otherwise invalid files), and pass it from the workflow during test-signing.

Remove the switch once signing-policy-slug moves to release-signing with a
production CA-issued certificate, so release builds enforce a fully trusted chain.
2026-10-18 23:35:16 +08:00
SoftFever f1428b72b8 ci(windows): wire SignPath test-signing for portable bundle
Add SignPath artifact configuration (windows-portable-v1) signing only the
first-party binaries orca-slicer.exe and OrcaSlicer.dll, plus verification
and inventory scripts. The Windows build job uploads the unsigned portable
bundle, submits it to SignPath (test-signing), verifies the returned
signatures, and rebuilds the installer/zip from the signed binaries.

project-slug matches the SignPath project (OrcaSlicer).
2026-10-18 23:35:16 +08:00
SoftFever 3880f2c58e Bump version to 2.5.0-alpha 2026-10-16 18:52:11 +08:00
15 changed files with 536 additions and 33 deletions

No files matched your search

+4
View File
@@ -21,6 +21,8 @@ on:
- 'scripts/test_build_win.ps1'
- 'build_release_macos.sh'
- 'scripts/build_preset_cache.*'
- 'signpath/**'
- 'scripts/*.ps1'
- 'scripts/flatpak/**'
- 'scripts/msix/**'
- 'tests/**'
@@ -38,11 +40,13 @@ on:
- 'version.inc'
- ".github/workflows/build_*.yml"
- ".github/workflows/unit_tests*.yml"
- 'signpath/**'
- 'build_linux.sh'
- 'build_win.bat'
- 'scripts/test_build_win.ps1'
- 'build_release_macos.sh'
- 'scripts/build_preset_cache.*'
- 'scripts/*.ps1'
- 'scripts/flatpak/**'
- 'scripts/msix/**'
- 'tests/**'
+118 -3
View File
@@ -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,19 +508,129 @@ jobs:
retention-days: 5
if-no-files-found: error
# 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: signpath_windows_binaries${{ env.ARCH_SUFFIX }}_unsigned
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-binaries
retention-days: 1
if-no-files-found: error
# 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: 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
wait-for-completion-timeout-in-seconds: 10800
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries
- name: Verify Windows binary signatures
if: env.SIGN_WINDOWS == 'true'
shell: pwsh
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries'
- name: Replace Windows binaries with signed output
if: env.SIGN_WINDOWS == 'true'
shell: pwsh
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: env.SIGN_WINDOWS == 'true'
uses: actions/upload-artifact@v7
with:
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: 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: 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
wait-for-completion-timeout-in-seconds: 3600
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer
- name: Verify Windows installer signature
if: env.SIGN_WINDOWS == 'true'
shell: pwsh
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: env.SIGN_WINDOWS == 'true'
shell: pwsh
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'
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
shell: cmd
run: '"C:/Program Files/7-Zip/7z.exe" a -tzip OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer'
shell: pwsh
run: |
$zipPath = "OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip"
Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue
& "C:/Program Files/7-Zip/7z.exe" a -tzip $zipPath "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer"
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
- name: Pack PDB
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
@@ -530,6 +644,7 @@ jobs:
with:
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer
if-no-files-found: error
- name: Upload artifacts Win installer
if: runner.os == 'Windows' && !vars.SELF_HOSTED
+12
View File
@@ -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)
@@ -45,10 +45,28 @@
<color type="primary" scheme_preference="dark">#00695C</color>
</branding>
<releases>
<release version="2.5.0-dev" date="2026-06-22" type="development">
<url type="details">https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds</url>
<release version="2.5.0-alpha" date="2026-10-14" type="development">
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_5_0_alpha</url>
<description>
<p>See the release page for detailed changelog.</p>
<p>See the release page for detailed changelog of OrcaSlicer 2.5.0-alpha release.</p>
</description>
</release>
<release version="2.4.2" date="2026-07-26">
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_2</url>
<description>
<p>See the release page for detailed changelog of OrcaSlicer 2.4.2 release.</p>
</description>
</release>
<release version="2.4.1" date="2026-06-26">
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_1</url>
<description>
<p>See the release page for detailed changelog of OrcaSlicer 2.4.1 release.</p>
</description>
</release>
<release version="2.4.0" date="2026-06-20">
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_0</url>
<description>
<p>See the release page for detailed changelog of OrcaSlicer 2.4.0 release.</p>
</description>
</release>
<release version="2.3.2" date="2025-03-23">
+48
View File
@@ -0,0 +1,48 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$ArtifactDirectory
)
$ErrorActionPreference = "Stop"
function Get-RelativePath {
param(
[Parameter(Mandatory = $true)]
[string]$Root,
[Parameter(Mandatory = $true)]
[string]$Path
)
$rootWithSeparator = $Root.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
$rootUri = [Uri]$rootWithSeparator
$pathUri = [Uri]$Path
[Uri]::UnescapeDataString($rootUri.MakeRelativeUri($pathUri).ToString()).Replace("/", [IO.Path]::DirectorySeparatorChar)
}
$artifactRoot = (Resolve-Path -LiteralPath $ArtifactDirectory).Path
$binaries = Get-ChildItem -LiteralPath $artifactRoot -Recurse -File |
Where-Object { $_.Extension -in @(".exe", ".dll") } |
Sort-Object -Property FullName
if (-not $binaries) {
Write-Warning "No .exe or .dll files found under '$artifactRoot'."
exit 0
}
$binaries | ForEach-Object {
$signature = Get-AuthenticodeSignature -LiteralPath $_.FullName
[pscustomobject]@{
RelativePath = Get-RelativePath -Root $artifactRoot -Path $_.FullName
Status = $signature.Status
SignatureType = $signature.SignatureType
Signer = if ($signature.SignerCertificate) { $signature.SignerCertificate.Subject } else { "" }
SignerThumbprint = if ($signature.SignerCertificate) { $signature.SignerCertificate.Thumbprint } else { "" }
Timestamped = [bool]$signature.TimeStamperCertificate
TimeStamper = if ($signature.TimeStamperCertificate) { $signature.TimeStamperCertificate.Subject } else { "" }
}
} | Format-Table -AutoSize
+40
View File
@@ -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'."
+81
View File
@@ -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/"
+121
View File
@@ -0,0 +1,121 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$ArtifactDirectory,
# 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
# with the release certificate, so release builds enforce a fully trusted chain.
[switch]$AllowUntrustedRoot
)
$ErrorActionPreference = "Stop"
function Resolve-SignToolPath {
param(
[string]$ExplicitPath
)
if ($ExplicitPath) {
if (Test-Path -LiteralPath $ExplicitPath -PathType Leaf) {
return (Resolve-Path -LiteralPath $ExplicitPath).Path
}
throw "SignTool was not found at '$ExplicitPath'."
}
$fromPath = Get-Command -Name "signtool.exe" -ErrorAction SilentlyContinue
if ($fromPath) {
return $fromPath.Source
}
$candidateRoots = @(
"${env:ProgramFiles(x86)}\Windows Kits\10\bin",
"${env:ProgramFiles}\Windows Kits\10\bin"
) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container) }
foreach ($root in $candidateRoots) {
$candidate = Get-ChildItem -LiteralPath $root -Recurse -Filter "signtool.exe" -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "\\(x64|arm64)\\signtool\.exe$" } |
Sort-Object -Property FullName -Descending |
Select-Object -First 1
if ($candidate) {
return $candidate.FullName
}
}
throw "signtool.exe was not found. Install the Windows SDK or pass -SignToolPath."
}
$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 of $(@($Files).Count) files in: $artifactRoot"
foreach ($relativePath in $Files) {
$filePath = Join-Path $artifactRoot $relativePath
if (-not (Test-Path -LiteralPath $filePath -PathType Leaf)) {
throw "Expected signed file was not found: $filePath"
}
# Capture signtool output without letting native stderr (redirected via 2>&1)
# raise a terminating NativeCommandError under $ErrorActionPreference = 'Stop'.
$previousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
$output = & $signtool verify /pa /all /tw /v $filePath 2>&1
$exitCode = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $previousErrorActionPreference
}
$signature = Get-AuthenticodeSignature -LiteralPath $filePath
if ($exitCode -eq 0) {
Write-Host "Verified $relativePath ($($signature.SignerCertificate.Subject))"
continue
}
if ($AllowUntrustedRoot) {
# signtool interleaves its stdout (chain details) and stderr (the error)
# unpredictably, so the error text cannot be matched reliably. Use the
# 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).
$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 ($($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."
}
Write-Host "Authenticode verification passed."
# signtool exits non-zero for the untrusted test-cert root even when we accept
# it above, leaving $LASTEXITCODE = 1. The GitHub Actions pwsh wrapper exits
# with that lingering code, so reset it to report success explicitly.
exit 0
+21
View File
@@ -0,0 +1,21 @@
# SignPath configurations
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.
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.
## What gets signed
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.
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 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.
## `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`
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.
+25
View File
@@ -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>
+11
View File
@@ -0,0 +1,11 @@
<?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" product-name="OrcaSlicer" product-version="${version}">
<authenticode-sign />
</pe-file>
</zip-file>
</artifact-configuration>
+6 -2
View File
@@ -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 -24
View File
@@ -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"
+1 -1
View File
@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0")
endif()
set(SoftFever_VERSION "2.5.0-dev")
set(SoftFever_VERSION "2.5.0-alpha")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})