From 8aad8de2ac90604523a060bf273293b8204620ed Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 12:51:10 +0200 Subject: [PATCH 1/7] Test DigiCert STM 2026 credentials with smctl sign --keypair-alias Windows signing certificate expires 2026-08-19; security provisioned a new credential set (_2026-suffixed secrets, keypair-alias based). This is a spike to test whether smctl's own sign/verify subcommands work directly against the new credentials, without signtool.exe or smctl windows certsync, before deciding on a target architecture. Also adds an `os` workflow_dispatch input (all/linux/windows/macos, default all) so a single-platform signing test doesn't require running the full multi-platform release pipeline. --- .../workflows/actions/sign-files/action.yml | 11 ++--- .github/workflows/create-release.yml | 42 +++++++++++++------ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/.github/workflows/actions/sign-files/action.yml b/.github/workflows/actions/sign-files/action.yml index cba83aa696b..6bd644985b7 100644 --- a/.github/workflows/actions/sign-files/action.yml +++ b/.github/workflows/actions/sign-files/action.yml @@ -95,13 +95,13 @@ runs: } Write-Output "::endgroup::" - - name: Sign files with signtool + - name: Sign files with smctl shell: pwsh env: SM_CLIENT_CERT_FILE: ${{ steps.setup-cert.outputs.SM_CLIENT_CERT_FILE }} run: | Write-Output "::group::Check for required environment variables" - $requiredEnvVars = @('SM_HOST', 'SM_API_KEY', 'SM_CLIENT_CERT_FILE', 'SM_CLIENT_CERT_PASSWORD', 'CERT_FINGERPRINT') + $requiredEnvVars = @('SM_HOST', 'SM_API_KEY', 'SM_CLIENT_CERT_FILE', 'SM_CLIENT_CERT_PASSWORD', 'KEYPAIR_ALIAS') foreach ($envVar in $requiredEnvVars) { if (-not $(Get-Item -Path "Env:$envVar" -ErrorAction SilentlyContinue)) { Write-Output "::error title=Missing environment variable::Environment variable $envVar is not set." @@ -110,19 +110,16 @@ runs: Write-Output "All env var correctly set." } Write-Output "::endgroup::" - Write-Output "::group::Sync certificates" - smctl windows certsync - Write-Output "::endgroup::" # Sign each file that will be bundled in the installer $paths = "${{ inputs.paths }}" -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } foreach ($path in $paths) { Write-Output "::group::Signing ${path}" - signtool.exe sign /sha1 $env:CERT_FINGERPRINT /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 ${{ inputs.signtools-extra-args }} $path + smctl sign --keypair-alias $env:KEYPAIR_ALIAS --input $path if ($LASTEXITCODE -ne 0) { Write-Output "::error title=Signing error::Error while signing ${path}" exit 1 } - signtool.exe verify /v /pa $path + smctl sign verify --input $path if ($LASTEXITCODE -ne 0) { Write-Output "::error title=Verify signature error::Error while verifying ${path}" exit 1 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 04ebc42003e..05402843b77 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -14,6 +14,16 @@ on: required: false type: boolean default: true + os: + description: "Which OS builds to run" + required: false + type: choice + options: + - all + - linux + - windows + - macos + default: all env: NFPM_VERSION: "2.43.1" @@ -124,6 +134,7 @@ jobs: make-tarball: runs-on: ubuntu-latest needs: [configure] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'linux' }} steps: - uses: actions/checkout@v6 with: @@ -161,6 +172,7 @@ jobs: make-arm64-tarball: runs-on: ubuntu-latest needs: [configure] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'linux' }} steps: - uses: actions/checkout@v6 with: @@ -248,6 +260,7 @@ jobs: make-installer-linux: runs-on: ubuntu-latest needs: [configure] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'linux' }} strategy: matrix: arch: [x86_64, aarch64] @@ -310,6 +323,7 @@ jobs: test-tarball-linux: runs-on: ubuntu-latest needs: [configure, make-tarball] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'linux' }} steps: - uses: actions/checkout@v6 with: @@ -350,6 +364,7 @@ jobs: make-installer-win: runs-on: windows-latest needs: [configure] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'windows' }} steps: - uses: actions/checkout@v6 @@ -389,12 +404,12 @@ jobs: ./package/pkg-working/bin/tools/pandoc.exe ./package/pkg-working/bin/quarto.js env: - # those environment variables are required to sign with signtool - SM_HOST: ${{ secrets.SM_HOST }} - SM_API_KEY: ${{ secrets.SM_API_KEY }} - SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }} - SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }} - CERT_FINGERPRINT: ${{ secrets.SM_CLIENT_CERT_FINGERPRINT }} + # DigiCert STM 2026 credential set -- testing smctl sign --keypair-alias + SM_HOST: ${{ secrets.SM_HOST_2026 }} + SM_API_KEY: ${{ secrets.SM_API_KEY_2026 }} + SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64_2026 }} + SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD_2026 }} + KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS_2026 }} - name: Make Installer run: | @@ -409,12 +424,12 @@ jobs: paths: ./package/out/quarto-${{needs.configure.outputs.version}}-win.msi signtools-extra-args: /d "Quarto CLI" env: - # those environment variables are required to sign with signtool - SM_HOST: ${{ secrets.SM_HOST }} - SM_API_KEY: ${{ secrets.SM_API_KEY }} - SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }} - SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }} - CERT_FINGERPRINT: ${{ secrets.SM_CLIENT_CERT_FINGERPRINT }} + # DigiCert STM 2026 credential set -- testing smctl sign --keypair-alias + SM_HOST: ${{ secrets.SM_HOST_2026 }} + SM_API_KEY: ${{ secrets.SM_API_KEY_2026 }} + SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64_2026 }} + SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD_2026 }} + KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS_2026 }} - name: Upload Artifact uses: actions/upload-artifact@v7 @@ -431,6 +446,7 @@ jobs: test-zip-win: runs-on: windows-latest needs: [configure, make-installer-win] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'windows' }} steps: - uses: actions/checkout@v6 with: @@ -479,6 +495,7 @@ jobs: make-installer-mac: runs-on: macos-latest needs: [configure] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'macos' }} steps: - uses: actions/checkout@v6 with: @@ -548,6 +565,7 @@ jobs: test-zip-mac: runs-on: macos-latest needs: [configure, make-installer-mac] + if: ${{ github.event_name != 'workflow_dispatch' || inputs.os == 'all' || inputs.os == 'macos' }} steps: - uses: actions/checkout@v6 with: From 80e5a0360533b92a897ffa73d838f0420def0280 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 12:58:11 +0200 Subject: [PATCH 2/7] Remove dead signtools-extra-args input smctl sign --keypair-alias has no equivalent flag (per DigiCert's docs), and the new sign command never consumed this input, so the MSI description arg was being silently dropped. Remove the now-unused input and its caller rather than imply it's still honored. --- .github/workflows/actions/sign-files/action.yml | 2 -- .github/workflows/create-release.yml | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/workflows/actions/sign-files/action.yml b/.github/workflows/actions/sign-files/action.yml index 6bd644985b7..2480f38ca53 100644 --- a/.github/workflows/actions/sign-files/action.yml +++ b/.github/workflows/actions/sign-files/action.yml @@ -4,8 +4,6 @@ inputs: paths: description: "Paths to sign" required: true - signtools-extra-args: - description: "Additionnal arguments to pass to signtool" outputs: cert_path: description: "certificate path" diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 05402843b77..1cddf477931 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -422,7 +422,6 @@ jobs: uses: ./.github/workflows/actions/sign-files with: paths: ./package/out/quarto-${{needs.configure.outputs.version}}-win.msi - signtools-extra-args: /d "Quarto CLI" env: # DigiCert STM 2026 credential set -- testing smctl sign --keypair-alias SM_HOST: ${{ secrets.SM_HOST_2026 }} From f459c823aae5e8fa3be6ed2a3ae12bf10c308ec6 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 13:12:31 +0200 Subject: [PATCH 3/7] Pin digest/signature algorithm and explicit timestamping for smctl sign smctl's --sigalg/--digalg default to "whatever the signing tool used supports" per DigiCert docs, unlike the old signtool.exe invocation which pinned /td SHA256 /fd SHA256 explicitly. Pin both rather than rely on an undocumented default. Also use --timestamp=true instead of the bare --timestamp flag, since DigiCert's docs only show the --timestamp=false boolean form. --- .github/workflows/actions/sign-files/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions/sign-files/action.yml b/.github/workflows/actions/sign-files/action.yml index 2480f38ca53..635459574f9 100644 --- a/.github/workflows/actions/sign-files/action.yml +++ b/.github/workflows/actions/sign-files/action.yml @@ -112,7 +112,7 @@ runs: $paths = "${{ inputs.paths }}" -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } foreach ($path in $paths) { Write-Output "::group::Signing ${path}" - smctl sign --keypair-alias $env:KEYPAIR_ALIAS --input $path + smctl sign --keypair-alias $env:KEYPAIR_ALIAS --input $path --digalg SHA256 --sigalg SHA256 --timestamp=true if ($LASTEXITCODE -ne 0) { Write-Output "::error title=Signing error::Error while signing ${path}" exit 1 From 07d43a83b7c89cdba775f6c40d40dd94ac8afae1 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 13:17:17 +0200 Subject: [PATCH 4/7] Guard against publishing a release from a partial-platform run The new os selector could skip Linux/macOS installer jobs while configure still bumped version.txt and pushed a tag (gated only on publish-release, not os). publish-release itself then auto-skips since its skipped needs aren't successes, but cleanup-when-failure only fires on failure()/cancelled(), not skipped -- leaving an orphaned version/tag commit with no corresponding release. Fail fast instead of publishing partial-platform releases. --- .github/workflows/create-release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 1cddf477931..67ab2821955 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -53,6 +53,12 @@ jobs: if: ${{ inputs.publish-release }} uses: ./.github/workflows/actions/prevent-rerun + - name: Guard against partial-platform release publish + if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish-release && inputs.os != 'all' }} + run: | + echo "::error::Cannot publish a release when os is not 'all' (got '${{ inputs.os }}'). Set publish-release=false for a single-platform test run, or os=all to publish a full release." + exit 1 + # we _also_ need npm, specifically for webui/preview - uses: actions/setup-node@v6 with: From 8b3161a5660df98eb8c23d1517ae147149091bf4 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 13:23:11 +0200 Subject: [PATCH 5/7] Update Windows signing doc for the smctl sign --keypair-alias flow llm-docs/code-signing-installers.md still described the old certsync/signtool.exe/CERT_FINGERPRINT flow, which would mislead anyone rotating DigiCert credentials or debugging signing into configuring the old secret set. --- llm-docs/code-signing-installers.md | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/llm-docs/code-signing-installers.md b/llm-docs/code-signing-installers.md index 642fe81b487..293c724ec84 100644 --- a/llm-docs/code-signing-installers.md +++ b/llm-docs/code-signing-installers.md @@ -170,17 +170,19 @@ Prevention: dispatch `create-release.yml` (publish-release=false) on the branch ### Cert provider -Posit signs via **DigiCert ONE / KeyLocker** (cloud-held EV cert). Local CLI is `smctl` (Software Trust Manager CLI) plus the standard `signtool.exe` from the Windows 10 SDK / App Certification Kit. The private key never leaves DigiCert's HSM — `smctl windows certsync` syncs the public cert into the local Windows cert store so `signtool` can reference it by thumbprint. +Posit signs via **DigiCert ONE / Software Trust Manager (STM)** (cloud-held EV cert). Local CLI is `smctl` (Software Trust Manager CLI). The private key never leaves DigiCert's HSM — `smctl sign --keypair-alias` talks to DigiCert directly to sign, with no local Windows cert-store sync needed. + +> **2026 credential rotation:** the cert active through mid-2026 used a SHA-1 fingerprint (`SM_CLIENT_CERT_FINGERPRINT` / `CERT_FINGERPRINT`) with `smctl windows certsync` + `signtool.exe`. The cert renewed for 2026 uses a keypair-alias model instead (`SM_KEYPAIR_ALIAS_2026` / `KEYPAIR_ALIAS`), signing directly via `smctl sign` with no `signtool.exe` invocation and no cert-store sync step. Secrets for the new cert carry a `_2026` suffix; the job's `env:` block maps them to the unsuffixed names below. Expect a similar suffix bump on each future annual renewal. ### Secrets → env vars → purpose -| Secret | Env var on sign step | Used for | -| ---------------------------- | ------------------------- | --------------------------------------------------------------------------------- | -| `SM_HOST` | `SM_HOST` | DigiCert tenant host URL | -| `SM_API_KEY` | `SM_API_KEY` | DigiCert API auth | -| `SM_CLIENT_CERT_FILE_B64` | `SM_CLIENT_CERT_FILE_B64` | Base64 of client auth PFX (decoded to `.build\certificates\codesign.pfx`) | -| `SM_CLIENT_CERT_PASSWORD` | `SM_CLIENT_CERT_PASSWORD` | PFX password | -| `SM_CLIENT_CERT_FINGERPRINT` | `CERT_FINGERPRINT` | SHA-1 thumbprint passed to `signtool /sha1` to select the cert in the local store | +| Secret (env var maps `_2026`-suffixed secret → unsuffixed name below) | Env var on sign step | Used for | +| --- | --- | --- | +| `SM_HOST_2026` | `SM_HOST` | DigiCert tenant host URL (must be `https://clientauth.one.digicert.com`) | +| `SM_API_KEY_2026` | `SM_API_KEY` | DigiCert API auth | +| `SM_CLIENT_CERT_FILE_B64_2026` | `SM_CLIENT_CERT_FILE_B64` | Base64 of client auth PFX (decoded to `.build\certificates\codesign.pfx`) | +| `SM_CLIENT_CERT_PASSWORD_2026` | `SM_CLIENT_CERT_PASSWORD` | PFX password | +| `SM_KEYPAIR_ALIAS_2026` | `KEYPAIR_ALIAS` | Keypair alias passed to `smctl sign --keypair-alias`, replaces the old cert-fingerprint selection | ### Signing flow — `.github/workflows/actions/sign-files` @@ -188,22 +190,20 @@ Composite action invoked **twice** per release in `make-installer-win`: 1. **Before MSI build** — sign every binary that will be bundled by WiX: `quarto.exe` (launcher), `deno.exe`, `esbuild.exe`, `dart.exe`, `deno_dom/plugin.dll`, `typst-gather.exe`, `pandoc.exe`, plus the `quarto.js` bundle. -2. **After MSI build** — sign the resulting `quarto--win.msi`, passing `signtools-extra-args: /d "Quarto CLI"` so the SmartScreen / UAC prompt shows a friendly description. +2. **After MSI build** — sign the resulting `quarto--win.msi`. `smctl sign` has no equivalent to signtool's `/d` description flag, so the MSI's SmartScreen / UAC description is whatever the MSI's own metadata provides, not an explicit signing-time string. Each invocation runs these steps (`.github/workflows/actions/sign-files/action.yml`): 1. **Setup client cert** — fail-fast if `SM_CLIENT_CERT_FILE_B64` missing; base64-decode into `.build\certificates\codesign.pfx` (cached across the second invocation). -2. **Install smctl** if absent — downloads `smtools-windows-x64.msi` from `rstudio-buildtools.s3.amazonaws.com` with retry-on-failure (transient S3 failures previously caused silent install failures); runs `msiexec /qn /log`; verifies `smctl.exe` is on disk; adds DigiCert install dir + Windows Kits App Certification Kit (signtool) to `PATH`. +2. **Install smctl** if absent — downloads `smtools-windows-x64.msi` from `rstudio-buildtools.s3.amazonaws.com` with retry-on-failure (transient S3 failures previously caused silent install failures); runs `msiexec /qn /log`; verifies `smctl.exe` is on disk. Also adds the Windows Kits App Certification Kit (`signtool.exe`) to `PATH` if missing — `smctl sign` delegates the actual Windows signing operation to `signtool.exe` under the hood, so this stays required even though nothing in the workflow invokes `signtool.exe` directly. 3. **Verify all required env vars** are set, else fail with `::error`. -4. **`smctl windows certsync`** — pull the EV cert from DigiCert into the local Windows cert store. -5. **For each path** in the multi-line `paths:` input: +4. **For each path** in the multi-line `paths:` input: ``` - signtool.exe sign /sha1 $CERT_FINGERPRINT \ - /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 \ - - signtool.exe verify /v /pa + smctl sign --keypair-alias $KEYPAIR_ALIAS --input \ + --digalg SHA256 --sigalg SHA256 --timestamp=true + smctl sign verify --input ``` - `/tr` adds an RFC 3161 timestamp (signature stays valid after cert expiry); `/fd SHA256` uses SHA-256 file digest; `/pa` verifies against the default authentication policy (the one SmartScreen uses). + `--digalg`/`--sigalg` are pinned explicitly because DigiCert's docs say they otherwise default to "whatever the underlying signing tool supports" rather than a fixed value. `--timestamp=true` keeps the signature valid after cert expiry (RFC 3161-style); DigiCert's own timestamp authority is used, without an explicit `--tsa-url` pin — that flag only records the TSA URL in signature metadata, it doesn't select which server is used. Any single signing or verify failure fails the whole step. From bdc19c2b9eefdc4a3bc7bf535378f2f181087f11 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 15:37:07 +0200 Subject: [PATCH 6/7] Address Opus code-review findings on the DigiCert signing migration Reword the sign-step env comment now that this is the production path, not a spike ("testing" was accurate pre-merge, misleading after). Bump llm-docs frontmatter to the actual merge-base/date and drop an unverified SM_HOST value claim traced to a second-hand doc handoff. --- .github/workflows/create-release.yml | 4 ++-- llm-docs/code-signing-installers.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 67ab2821955..f1e94a183b2 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -410,7 +410,7 @@ jobs: ./package/pkg-working/bin/tools/pandoc.exe ./package/pkg-working/bin/quarto.js env: - # DigiCert STM 2026 credential set -- testing smctl sign --keypair-alias + # DigiCert STM 2026 credential set (signed via smctl sign --keypair-alias) SM_HOST: ${{ secrets.SM_HOST_2026 }} SM_API_KEY: ${{ secrets.SM_API_KEY_2026 }} SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64_2026 }} @@ -429,7 +429,7 @@ jobs: with: paths: ./package/out/quarto-${{needs.configure.outputs.version}}-win.msi env: - # DigiCert STM 2026 credential set -- testing smctl sign --keypair-alias + # DigiCert STM 2026 credential set (signed via smctl sign --keypair-alias) SM_HOST: ${{ secrets.SM_HOST_2026 }} SM_API_KEY: ${{ secrets.SM_API_KEY_2026 }} SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64_2026 }} diff --git a/llm-docs/code-signing-installers.md b/llm-docs/code-signing-installers.md index 293c724ec84..21bf66bb577 100644 --- a/llm-docs/code-signing-installers.md +++ b/llm-docs/code-signing-installers.md @@ -1,6 +1,6 @@ --- -main_commit: a8d0dcfee -analyzed_date: 2026-05-19 +main_commit: c3e0f5cae +analyzed_date: 2026-07-24 key_files: - package/src/macos/installer.ts - package/src/windows/installer.ts @@ -178,7 +178,7 @@ Posit signs via **DigiCert ONE / Software Trust Manager (STM)** (cloud-held EV c | Secret (env var maps `_2026`-suffixed secret → unsuffixed name below) | Env var on sign step | Used for | | --- | --- | --- | -| `SM_HOST_2026` | `SM_HOST` | DigiCert tenant host URL (must be `https://clientauth.one.digicert.com`) | +| `SM_HOST_2026` | `SM_HOST` | DigiCert tenant host URL | | `SM_API_KEY_2026` | `SM_API_KEY` | DigiCert API auth | | `SM_CLIENT_CERT_FILE_B64_2026` | `SM_CLIENT_CERT_FILE_B64` | Base64 of client auth PFX (decoded to `.build\certificates\codesign.pfx`) | | `SM_CLIENT_CERT_PASSWORD_2026` | `SM_CLIENT_CERT_PASSWORD` | PFX password | From 28b145ad061b92c31e106c07fea4982b831bec7a Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 24 Jul 2026 15:44:45 +0200 Subject: [PATCH 7/7] Document verify-equivalence and /d-flag findings in code-signing doc These are the two facts other maintainers will want next time this file needs touching: smctl's verify step turns out to shell out to signtool internally (confirmed from CI logs, not just docs), and the dropped MSI description flag was already invisible everywhere before removal, so its loss isn't a regression. --- llm-docs/code-signing-installers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/llm-docs/code-signing-installers.md b/llm-docs/code-signing-installers.md index 21bf66bb577..f0adaa82c38 100644 --- a/llm-docs/code-signing-installers.md +++ b/llm-docs/code-signing-installers.md @@ -205,8 +205,12 @@ Each invocation runs these steps (`.github/workflows/actions/sign-files/action.y ``` `--digalg`/`--sigalg` are pinned explicitly because DigiCert's docs say they otherwise default to "whatever the underlying signing tool supports" rather than a fixed value. `--timestamp=true` keeps the signature valid after cert expiry (RFC 3161-style); DigiCert's own timestamp authority is used, without an explicit `--tsa-url` pin — that flag only records the TSA URL in signature metadata, it doesn't select which server is used. + `smctl sign verify` shells out to `signtool verify /pa` internally (confirmed from live CI job logs, not just docs) — a verify failure surfaces as a `signtool` error, same as before the migration. + Any single signing or verify failure fails the whole step. +**On the dropped `/d` description flag:** checked the pre-migration MSI (built with `signtool /d "Quarto CLI"`) via `certutil -dump`, `signtool verify /v /pa`, and Explorer's Digital Signatures > Details > Additional Information dialog — none of them surfaced the description text even though the flag was set. Removing it during the migration has no observed user-facing effect, since the field wasn't visibly rendering when present either. + ### Windows launcher `package/launcher/Cargo.toml` builds `quarto.exe` (Rust launcher) just before signing in `make-installer-win`. The Rust launcher is what the user actually runs; the underlying `quarto.cmd` / `deno` executes from inside the install dir. All of `quarto.exe`, `deno.exe`, and the bundled tools get signed individually so SmartScreen accepts the process tree, not just the MSI.