Skip to content

Commit d901df4

Browse files
yashdsarafclaude
andcommitted
CD: migrate nupkg signing from PFX to GCP KMS via Jsign
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>
1 parent 085ce6a commit d901df4

3 files changed

Lines changed: 83 additions & 17 deletions

File tree

.github/workflows/cd.yml

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,50 @@ jobs:
2626
uses: actions/setup-dotnet@v3
2727
with:
2828
dotnet-version: 6.0.x
29+
- name: Setup GCP credentials
30+
working-directory: ${{ github.workspace }}
31+
run: |
32+
printf '%s' "$GCP_SA_KEY_RAW" > $RUNNER_TEMP/gcp-sa-key.json
33+
chmod 600 $RUNNER_TEMP/gcp-sa-key.json
34+
echo "GOOGLE_APPLICATION_CREDENTIALS=$RUNNER_TEMP/gcp-sa-key.json" >> $GITHUB_ENV
35+
env:
36+
GCP_SA_KEY_RAW: ${{ secrets.GCP_SA_KEY }}
37+
shell: bash
38+
- name: Install google-auth
39+
working-directory: ${{ github.workspace }}
40+
run: python3 -m pip install google-auth requests
41+
shell: bash
42+
- name: Setup Java
43+
uses: actions/setup-java@v4
44+
with:
45+
distribution: 'temurin'
46+
java-version: '17'
2947
- name: Run Integration Tests
3048
env:
3149
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
3250
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
3351
run: dotnet test BrowserStackLocalIntegrationTests --no-build -p:Configuration=Release
3452
- name: Pack NuGet Package
3553
run: msbuild BrowserStackLocal -t:pack -p:Configuration=Release
36-
- name: Setup nuget
37-
uses: nuget/setup-nuget@v1
38-
with:
39-
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
40-
nuget-version: '5.x'
41-
- name: Create PFX certificate
42-
id: createPfx
43-
shell: pwsh
44-
env:
45-
PFX_CONTENT: ${{ secrets.BASE64_PFX_CONTENT }}
46-
run: |
47-
$pfxPath = Join-Path -Path $env:RUNNER_TEMP -ChildPath "cert.pfx";
48-
$encodedBytes = [System.Convert]::FromBase64String($env:PFX_CONTENT);
49-
Set-Content $pfxPath -Value $encodedBytes -AsByteStream;
50-
Write-Output "::set-output name=PFX_PATH::$pfxPath";
5154
- name: Sign Nuget Package
52-
run: nuget sign .\BrowserStackLocal\bin\Release\*.nupkg -certificatePath "${{ steps.createPfx.outputs.PFX_PATH }}" -certificatePassword "${{secrets.CERT_PASSWORD}}" -Timestamper "http://timestamp.comodoca.com"
55+
working-directory: ${{ github.workspace }}
56+
run: |
57+
chmod +x ./scripts/sign_nupkg.sh
58+
./scripts/sign_nupkg.sh
59+
shell: bash
5360
- name: Save artifact
5461
uses: actions/upload-artifact@v4
5562
with:
5663
name: BrowserStackLocal.nupkg
5764
path: .\BrowserStackLocal\BrowserStackLocal\bin\Release\*.nupkg
5865
- name: Push package to Nuget Repository
59-
run: nuget push **\*.nupkg -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{secrets.NUGET_API_KEY}}
66+
working-directory: ${{ github.workspace }}
67+
run: dotnet nuget push BrowserStackLocal/BrowserStackLocal/bin/Release/*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json
68+
env:
69+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
70+
shell: bash
71+
- name: Cleanup credentials
72+
if: always()
73+
working-directory: ${{ github.workspace }}
74+
run: rm -f $RUNNER_TEMP/gcp-sa-key.json
75+
shell: bash

comodo_signing_cert.crt

1.57 KB
Binary file not shown.

scripts/sign_nupkg.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/bin/bash
2+
set -e
3+
4+
if [ -z "$GOOGLE_APPLICATION_CREDENTIALS" ] || [ ! -f "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
5+
echo "ERROR: GOOGLE_APPLICATION_CREDENTIALS is not set or file does not exist"
6+
exit 1
7+
fi
8+
9+
# Exchange SA credentials for an OAuth access token scoped to cloudkms.
10+
# gcloud is NOT installed on windows-latest runners; google-auth Python lib
11+
# is the reliable path (see browserstack-csharp-sdk PR #704 for the
12+
# enumeration of failed alternatives).
13+
GCP_ACCESS_TOKEN=$(python3 -c "
14+
import google.auth
15+
import google.auth.transport.requests
16+
creds, _ = google.auth.default(scopes=['https://www.googleapis.com/auth/cloudkms'])
17+
creds.refresh(google.auth.transport.requests.Request())
18+
print(creds.token)
19+
")
20+
21+
# Download Jsign 7.0 + SHA-256 verify (supply-chain integrity).
22+
JSIGN_VERSION="7.0"
23+
JSIGN_SHA256="325df319621e7fa74384c8852efdb5828871bf6405648a4c621ee5fc37c59b6c"
24+
curl -fsL "https://github.com/ebourg/jsign/releases/download/${JSIGN_VERSION}/jsign-${JSIGN_VERSION}.jar" -o jsign.jar
25+
echo "${JSIGN_SHA256} jsign.jar" | sha256sum -c - || { echo "ERROR: Jsign checksum verification failed"; exit 1; }
26+
27+
# Sign every nupkg produced by the pack step.
28+
SIGNED_ANY=0
29+
for NUPKG in BrowserStackLocal/BrowserStackLocal/bin/Release/*.nupkg; do
30+
if [ ! -f "$NUPKG" ]; then
31+
continue
32+
fi
33+
echo "Signing $NUPKG"
34+
java -jar jsign.jar \
35+
--storetype GOOGLECLOUD \
36+
--storepass "$GCP_ACCESS_TOKEN" \
37+
--keystore "projects/browserstack-production/locations/us-east1/keyRings/prod-comodo-win-cert-keyring" \
38+
--alias "prod-comodo-win-cert-key/cryptoKeyVersions/1" \
39+
--certfile comodo_signing_cert.crt \
40+
--tsaurl http://timestamp.sectigo.com \
41+
"$NUPKG"
42+
SIGNED_ANY=1
43+
done
44+
45+
if [ "$SIGNED_ANY" -eq 0 ]; then
46+
echo "ERROR: no .nupkg files found under BrowserStackLocal/BrowserStackLocal/bin/Release/"
47+
exit 1
48+
fi
49+
50+
echo "Signing complete"

0 commit comments

Comments
 (0)