Compare commits

...
Author SHA1 Message Date
SoftFever 4c74ab393b Sign the uninstaller in Windows release installers 2026-10-19 13:07:53 +08:00
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
16 changed files with 595 additions and 32 deletions

No files matched your search

+4
View File
@@ -21,6 +21,8 @@ on:
- 'scripts/test_build_win.ps1' - 'scripts/test_build_win.ps1'
- 'build_release_macos.sh' - 'build_release_macos.sh'
- 'scripts/build_preset_cache.*' - 'scripts/build_preset_cache.*'
- 'signpath/**'
- 'scripts/*.ps1'
- 'scripts/flatpak/**' - 'scripts/flatpak/**'
- 'scripts/msix/**' - 'scripts/msix/**'
- 'tests/**' - 'tests/**'
@@ -38,11 +40,13 @@ on:
- 'version.inc' - 'version.inc'
- ".github/workflows/build_*.yml" - ".github/workflows/build_*.yml"
- ".github/workflows/unit_tests*.yml" - ".github/workflows/unit_tests*.yml"
- 'signpath/**'
- 'build_linux.sh' - 'build_linux.sh'
- 'build_win.bat' - 'build_win.bat'
- 'scripts/test_build_win.ps1' - 'scripts/test_build_win.ps1'
- 'build_release_macos.sh' - 'build_release_macos.sh'
- 'scripts/build_preset_cache.*' - 'scripts/build_preset_cache.*'
- 'scripts/*.ps1'
- 'scripts/flatpak/**' - 'scripts/flatpak/**'
- 'scripts/msix/**' - 'scripts/msix/**'
- 'tests/**' - 'tests/**'
+146 -3
View File
@@ -424,6 +424,10 @@ jobs:
"BUILD_DIR=build" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8 "BUILD_DIR=build" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
"ARCH_SUFFIX=_x64" | 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 - name: setup MSVC
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -504,19 +508,157 @@ jobs:
retention-days: 5 retention-days: 5
if-no-files-found: error if-no-files-found: error
# makensis generates the uninstaller while it compiles the installer, so a first
# installer build exports it to be signed with the binaries, and "Create installer
# Win" embeds the signed copy. See src/dev-utils/platform/msw/uninstaller-signing.nsh.
- name: Export the Windows uninstaller for SignPath
if: env.SIGN_WINDOWS == 'true'
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
shell: pwsh
run: |
$bundle = "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer".Replace('\', '/')
# Forward slashes: cpack drops the backslashes from makensis arguments.
$uninstaller = "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/Uninstall.exe".Replace('\', '/')
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $uninstaller) | Out-Null
cpack -G NSIS -D CPACK_INSTALL_CMAKE_PROJECTS= -D "CPACK_INSTALLED_DIRECTORIES=$bundle;." -D "CPACK_NSIS_EXECUTABLE_PRE_ARGUMENTS=/DORCA_EXPORT_UNINSTALLER=$uninstaller"
# Signs OrcaSlicer's own binaries, the uninstaller 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" -Path "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/Uninstall.exe" -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" -Exclude Uninstall.exe
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's # 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. # 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 - name: Create installer Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }} working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
run: | run: |
cpack -G NSIS $bundle = "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer".Replace('\', '/')
$signedUninstaller = @()
if ($env:SIGN_WINDOWS -eq 'true') {
$uninstaller = "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries/Uninstall.exe".Replace('\', '/')
$signedUninstaller = "-D", "CPACK_NSIS_EXECUTABLE_PRE_ARGUMENTS=/DORCA_SIGNED_UNINSTALLER=$uninstaller"
}
cpack -G NSIS -D CPACK_INSTALL_CMAKE_PROJECTS= -D "CPACK_INSTALLED_DIRECTORIES=$bundle;." @signedUninstaller
# Installs into a scratch directory and checks that every executable and DLL an
# installation puts on disk, the uninstaller included, carries a valid signature,
# ours or its vendor's. The hosted runner is discarded afterwards, so the
# installation is left in place.
- name: Verify every binary the installer installs is signed
if: env.SIGN_WINDOWS == 'true'
shell: pwsh
run: |
$installDir = Join-Path "${{ runner.temp }}" "orca-install-check"
$installer = "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe"
# NSIS requires /D= to come last.
$setup = Start-Process -FilePath $installer -ArgumentList "/S", "/D=$installDir" -Wait -PassThru
if ($setup.ExitCode -ne 0) { throw "The installer exited with code $($setup.ExitCode)." }
if (-not (Test-Path -LiteralPath (Join-Path $installDir "Uninstall.exe") -PathType Leaf)) {
throw "The installation in '$installDir' has no Uninstall.exe."
}
./scripts/verify-authenticode.ps1 -ArtifactDirectory $installDir
- 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 - name: Pack app
if: runner.os == 'Windows' if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }} working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
shell: cmd shell: pwsh
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' 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 - name: Pack PDB
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
@@ -530,6 +672,7 @@ jobs:
with: with:
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer
if-no-files-found: error
- name: Upload artifacts Win installer - name: Upload artifacts Win installer
if: runner.os == 'Windows' && !vars.SELF_HOSTED if: runner.os == 'Windows' && !vars.SELF_HOSTED
+17
View File
@@ -1355,6 +1355,23 @@ set (CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer") set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer")
set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer") set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer")
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command
# Version information of the installer and of the uninstaller it writes, the same as the
# binaries it installs carry (src/dev-utils/platform/msw/OrcaSlicer-version.rc.in):
# SignPath only signs them when ProductName and ProductVersion match the artifact
# configurations in signpath/. The include lets the release workflow embed a signed
# uninstaller. The NSIS strings are single-quoted and the paths use forward slashes
# because CPack writes this value into CPackConfig.cmake unescaped; !include only accepts
# a forward-slash path through !addincludedir.
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"
"!addincludedir '${CMAKE_SOURCE_DIR}/src/dev-utils/platform/msw'\n"
"!include 'uninstaller-signing.nsh'\n")
set(CPACK_WIX_UPGRADE_GUID "058245e8-20e0-4a95-9ab7-1acfe17ad511") set(CPACK_WIX_UPGRADE_GUID "058245e8-20e0-4a95-9ab7-1acfe17ad511")
set(CPACK_GENERATOR NSIS) set(CPACK_GENERATOR NSIS)
@@ -45,10 +45,28 @@
<color type="primary" scheme_preference="dark">#00695C</color> <color type="primary" scheme_preference="dark">#00695C</color>
</branding> </branding>
<releases> <releases>
<release version="2.5.0-dev" date="2026-06-22" type="development"> <release version="2.5.0-alpha" date="2026-10-14" type="development">
<url type="details">https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds</url> <url type="details">https://www.orcaslicer.com/wiki/releases/release_2_5_0_alpha</url>
<description> <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> </description>
</release> </release>
<release version="2.3.2" date="2025-03-23"> <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
+44
View File
@@ -0,0 +1,44 @@
# 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. Root files named in -Exclude came from outside the bundle, such as the exported
# uninstaller, and stay in -SignedDirectory.
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$SignedDirectory,
[Parameter(Mandatory = $true)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$Bundle,
[string[]]$Exclude = @()
)
$ErrorActionPreference = "Stop"
$signedRoot = (Resolve-Path -LiteralPath $SignedDirectory).Path.TrimEnd('\', '/')
$thirdPartyPrefix = "third-party" + [IO.Path]::DirectorySeparatorChar
$files = Get-ChildItem -LiteralPath $signedRoot -Recurse -File |
Where-Object { $_.DirectoryName -ne $signedRoot -or $_.Name -notin $Exclude }
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'."
+82
View File
@@ -0,0 +1,82 @@
# 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, plus any -Path files from outside the bundle,
# such as the exported uninstaller) 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")]
[Parameter(ParameterSetName = "Bundle")]
[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
$ownFiles = @($Path | Where-Object { $_ })
if ($PSCmdlet.ParameterSetName -eq "Bundle") {
$bundleRoot = (Resolve-Path -LiteralPath $Bundle).Path.TrimEnd('\', '/')
$ownFiles = @($OwnBinaries | ForEach-Object { Join-Path $bundleRoot $_ }) + $ownFiles
}
foreach ($file in $ownFiles) {
Test-VersionInfo $file
Copy-Item -LiteralPath $file -Destination $Destination -Force
Write-Host "Staged $(Split-Path -Leaf $file) ($ProductName $ProductVersion)"
}
if ($PSCmdlet.ParameterSetName -eq "Path") {
exit 0
}
$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
+23
View File
@@ -0,0 +1,23 @@
# 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 with the `Uninstall.exe` it installs.
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, the installer and the uninstaller 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 and its uninstaller 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`, and the installer's uninstaller. `scripts/stage-for-signpath.ps1 -Bundle` stages them for upload: `orca-slicer.exe`, `OrcaSlicer.dll` and `Uninstall.exe` at the root of the artifact ZIP, where the metadata restrictions apply, and every other unsigned `.exe`, `.dll` and `.pyd` of the bundle 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.
makensis generates the uninstaller while it compiles the installer, so it cannot be signed after the fact like the other files. Before the binaries are submitted, the workflow builds the installer once to export its uninstaller, and the final `cpack` embeds the signed copy in its place, through the hooks in `src/dev-utils/platform/msw/uninstaller-signing.nsh`. Both builds pack the same files with the same script, so the uninstaller is the same one the final installer would have generated. After the final `cpack`, the workflow installs the installer silently into a scratch directory on the runner and verifies that every binary the installation puts on disk, the uninstaller included, carries a valid signature.
## `windows-installer-v1`
Signs the NSIS installer `cpack` produces, `OrcaSlicer_Windows_Installer_<version>_<arch>.exe`, uploaded as the only file of its artifact.
+26
View File
@@ -0,0 +1,26 @@
<?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" />
<include path="Uninstall.exe" />
<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/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) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
if (WIN32) 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 () else ()
add_executable(OrcaSlicer OrcaSlicer.cpp OrcaSlicer.hpp) add_executable(OrcaSlicer OrcaSlicer.cpp OrcaSlicer.hpp)
endif () endif ()
@@ -216,7 +220,7 @@ if (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
endif() 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. # Generate debug symbols even in release mode.
if(MSVC) if(MSVC)
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>") 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" 2 ICON "@SLIC3R_RESOURCES_DIR@/images/OrcaSlicer.ico"
1 24 "OrcaSlicer.manifest" 1 24 "OrcaSlicer.manifest"
@@ -0,0 +1,19 @@
; Lets the Windows release workflow ship a signed uninstaller (see signpath/README.md).
; makensis generates the uninstaller while it compiles the installer, so it cannot be
; signed afterwards like the installed binaries. Instead a first compile exports it
; (/DORCA_EXPORT_UNINSTALLER=<path>), the export is signed together with the binaries, and
; the final compile embeds the signed copy in place of the uninstaller it generates
; (/DORCA_SIGNED_UNINSTALLER=<path>). Both compiles run the same script over the same
; installed files, so they generate the same uninstaller. A failed copy aborts the
; compile rather than leave the uninstaller unsigned.
;
; cpack passes the paths with forward slashes, which cmd's copy does not accept for its
; source, so they are converted first.
!ifdef ORCA_EXPORT_UNINSTALLER
!searchreplace ORCA_EXPORT_UNINSTALLER_PATH "${ORCA_EXPORT_UNINSTALLER}" "/" "\"
!uninstfinalize 'cmd /c copy /y "%1" "${ORCA_EXPORT_UNINSTALLER_PATH}"' = 0
!endif
!ifdef ORCA_SIGNED_UNINSTALLER
!searchreplace ORCA_SIGNED_UNINSTALLER_PATH "${ORCA_SIGNED_UNINSTALLER}" "/" "\"
!uninstfinalize 'cmd /c copy /y "${ORCA_SIGNED_UNINSTALLER_PATH}" "%1"' = 0
!endif
+1 -1
View File
@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
if(NOT DEFINED BBL_INTERNAL_TESTING) if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0") set(BBL_INTERNAL_TESTING "0")
endif() endif()
set(SoftFever_VERSION "2.5.0-dev") set(SoftFever_VERSION "2.5.0-alpha")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
SoftFever_VERSION_MATCH ${SoftFever_VERSION}) SoftFever_VERSION_MATCH ${SoftFever_VERSION})
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})