Skip to content

CD: migrate nupkg signing from PFX to GCP KMS via Jsign (LOC-6563)#59

Merged
yashdsaraf merged 1 commit into
masterfrom
LOC-6563-cd-kms-signing
Jun 4, 2026
Merged

CD: migrate nupkg signing from PFX to GCP KMS via Jsign (LOC-6563)#59
yashdsaraf merged 1 commit into
masterfrom
LOC-6563-cd-kms-signing

Conversation

@yashdsaraf
Copy link
Copy Markdown
Collaborator

@yashdsaraf yashdsaraf commented Jun 4, 2026

Summary

CD has been broken since the PFX certificate stored in BASE64_PFX_CONTENT expired on 2026-03-29. The next dispatch after merging #57 + #58 failed with NU3018: NotTimeValid at the signing step.

This PR migrates nupkg signing to the same KMS-backed pattern the C# SDK adopted in browserstack/browserstack-csharp-sdk#704 (merged 2026-03-31). Same KMS key, same Sectigo cert, same tool — Jsign 7.0 on the windows-latest runner talking to GCP Cloud KMS REST directly via OAuth.

Why Jsign and not nuget sign -CertificateFingerprint

  • Microsoft hard-blocks CNG keys for dotnet nuget sign (NU3001 Scenario 6)
  • signtool.exe (which supports KMS via /csp) cannot sign .nupkg (wrong format)
  • dotnet/sign is Azure-Key-Vault-only
  • Jsign 7.0 is the only tool the C# SDK team identified that supports both nupkg and Google Cloud KMS natively

Why no PoC

The reference PR #704 has been signing real production nupkgs to NuGet.org with this exact recipe since 2026-03-31. We're using the same KMS key, same cert, same tool, same runner, same Java distribution. The signing operation is artifact-agnostic; only the artifact path differs.

Changes

New files

  • comodo_signing_cert.crt (repo root) — public Sectigo cert, expires 2027-04-30. Identical bytes to the one in browserstack-binary/scripts/ and browserstack-csharp-sdk.
  • scripts/sign_nupkg.sh — google-auth token exchange + Jsign download + SHA-256 verify + sign loop.

.github/workflows/cd.yml

  • Removed: Setup nuget (the nuget/setup-nuget@v1 action), Create PFX certificate, Sign Nuget Package (the PFX one).
  • Added: Setup GCP credentials (using printf '%s'echo on Windows Git Bash corrupts multiline JSON), Install google-auth, Setup Java 17 (Temurin), Sign Nuget Package (the jsign one), Cleanup credentials step with if: always().
  • Changed: Push package to Nuget Repository now uses dotnet nuget push (the nuget.exe action was removed; dotnet nuget push ships with the SDK).
  • Several signing-related steps override working-directory: ${{ github.workspace }} because they need to see paths from the repo root (scripts/, comodo_signing_cert.crt).

GitHub Secrets

MUST ADD before merging this PR

Secret Contents
GCP_SA_KEY Raw JSON of the GCP Service Account key (NOT base64). Pull from Vault: /master/bigquery/service_account/local/windows_codesign/cert_file_json. Same SA the C# SDK PR #704 uses.

REMOVE in a follow-up cleanup PR (after one green CD run)

Secret Status
BASE64_PFX_CONTENT Obsolete
CERT_PASSWORD Obsolete

Don't delete in the same PR — keep one tag cycle in case of fast rollback.

Test plan

  • After GCP_SA_KEY secret is added, dispatch cd.yml from this branch via gh workflow run cd.yml --ref LOC-6563-cd-kms-signing or the Actions UI
  • New steps run in order: Setup GCP credentials → Install google-auth → Setup Java → ... → Pack NuGet Package → Sign Nuget Package → Save artifact → Push → Cleanup
  • Sign step output shows Signing BrowserStackLocal/BrowserStackLocal/bin/Release/BrowserStackLocal.3.1.0.nupkg followed by no error
  • BrowserStackLocal.3.1.0.nupkg lands on NuGet.org
  • dotnet nuget verify --all on the published nupkg from a fresh machine reports success with author signature anchored to the Sectigo cert
  • Cleanup credentials ran (visible even on failure paths because if: always())

Failure modes to watch for

  • storepass rejected by KMS: SA key doesn't have cloudkms.cryptoKeyVersions.useToSign IAM. The SA is already authorized for binary + csharp-sdk so this shouldn't happen, but if it does, check IAM bindings on projects/browserstack-production/locations/us-east1/keyRings/prod-comodo-win-cert-keyring.
  • python3 -m pip install google-auth fails on windows-latest: pin to a known version.
  • Jsign SHA-256 mismatch: GitHub release artifact rotated. Update JSIGN_SHA256 in scripts/sign_nupkg.sh to the current hash from https://github.com/ebourg/jsign/releases.

Reference

🤖 Generated with Claude Code

PR #57 + #58 shipped the binding's net6.0 + arm64/proxy/UA changes,
but the next CD dispatch failed at the signing step: the PFX
certificate stored in BASE64_PFX_CONTENT expired on 2026-03-29
(error NU3018 NotTimeValid).

Migrating to the same KMS-backed signing pattern the C# SDK adopted
in PR #704 (browserstack/browserstack-csharp-sdk, merged 2026-03-31).
Same KMS key, same Sectigo cert, same tool chain — only the artifact
path differs.

Why Jsign rather than `nuget sign -CertificateFingerprint`:
- Microsoft hard-blocks CNG keys for `dotnet nuget sign` (NU3001
  Scenario 6: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3001).
- `signtool.exe` (which does support KMS via CSP) cannot sign .nupkg.
- Jsign 7.0 is the only tool that supports both nupkg and Google
  Cloud KMS natively.

Changes
- New `comodo_signing_cert.crt` at repo root — public Sectigo cert
  (same bytes as browserstack-binary/scripts/ and csharp-sdk repo).
- New `scripts/sign_nupkg.sh` — exchanges SA creds for an OAuth
  access token via google-auth, downloads + SHA-256-verifies Jsign,
  signs each nupkg with `--storetype GOOGLECLOUD` and TSA
  http://timestamp.sectigo.com.
- `.github/workflows/cd.yml`:
  - Removed: `Setup nuget` (nuget/setup-nuget@v1), `Create PFX
    certificate`, `Sign Nuget Package` (the PFX/nuget-sign one).
  - Added: `Setup GCP credentials` (printf, not echo — `echo` on
    Windows Git Bash corrupts multiline JSON), `Install google-auth`,
    `Setup Java 17` (Temurin), `Sign Nuget Package` (the jsign one),
    `Cleanup credentials` with `if: always()`.
  - `Push package to Nuget Repository`: `nuget push` -> `dotnet nuget
    push` (since the nuget.exe action was removed; dotnet nuget push
    ships with the SDK already on the runner).

GitHub Secrets required before this branch ships:
- ADD: `GCP_SA_KEY` (raw JSON from Vault
  /master/bigquery/service_account/local/windows_codesign/cert_file_json)
- REMOVE after one green run (separate cleanup PR):
  `BASE64_PFX_CONTENT`, `CERT_PASSWORD`.

Tracks LOC-6563. References browserstack-csharp-sdk#704.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yashdsaraf yashdsaraf marked this pull request as ready for review June 4, 2026 23:11
@yashdsaraf yashdsaraf requested a review from a team as a code owner June 4, 2026 23:11
@yashdsaraf yashdsaraf merged commit 5db2cbd into master Jun 4, 2026
3 checks passed
@yashdsaraf yashdsaraf deleted the LOC-6563-cd-kms-signing branch June 4, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant