From d29928355b31a26df2b4cca096cfe76a190a3ea4 Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:57:38 -0700 Subject: [PATCH 1/2] FIX: Sign Windows native .pyd binaries with ESRP in official and non-official builds Signs the compiled ddbc_bindings.*.pyd extension with the CP-230012 Authenticode certificate before it is packaged into the wheel by setup.py bdist_wheel. Because the .pyd is built in a dedicated step before packaging, we sign it in place (no wheel unpack/repack), so wheel RECORD hashes stay correct. - Rewrite compound-esrp-code-signing-step.yml (previously disabled) into a working native-binary signer: EsrpMalwareScanning + EsrpCodeSigning (SigntoolSign+Verify) + Authenticode verification gate. - Wire signing into build-windows-single-stage.yml after Build PYD, add a post-bdist_wheel step that verifies the .pyd embedded in the wheel is Authenticode Valid (signing evidence). - Add signWindowsBinaries parameter (default true), threaded through the main pipeline; not gated on oneBranchType so signing runs in both Official and NonOfficial runs. AB#46467 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 72f9edcc-d3cb-4bec-beed-f90445857f80 --- .../build-release-package-pipeline.yml | 11 + .../stages/build-windows-single-stage.yml | 84 +++++ .../steps/compound-esrp-code-signing-step.yml | 348 ++++++++---------- 3 files changed, 245 insertions(+), 198 deletions(-) diff --git a/OneBranchPipelines/build-release-package-pipeline.yml b/OneBranchPipelines/build-release-package-pipeline.yml index b5abb6cee..b5b719cef 100644 --- a/OneBranchPipelines/build-release-package-pipeline.yml +++ b/OneBranchPipelines/build-release-package-pipeline.yml @@ -70,6 +70,14 @@ parameters: type: boolean default: true + # Authenticode-sign the Windows native .pyd extensions with ESRP (CP-230012). + # Enabled by default so BOTH Official and NonOfficial runs produce signed wheels. + # Disable only for fast dev iterations where ESRP signing is not needed. + - name: signWindowsBinaries + displayName: 'Sign Windows native .pyd binaries (ESRP)' + type: boolean + default: true + # ========================= # PLATFORM CONFIGURATIONS # ========================= @@ -396,6 +404,9 @@ extends: shortPyVer: ${{ config.pyVer }} architecture: ${{ config.arch }} oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Sign the native .pyd with ESRP (CP-230012) in both Official and + # NonOfficial runs. + signWindowsBinaries: ${{ parameters.signWindowsBinaries }} # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). odbcDependsOn: - ConsolidateOdbc diff --git a/OneBranchPipelines/stages/build-windows-single-stage.yml b/OneBranchPipelines/stages/build-windows-single-stage.yml index c49eac4db..68b0d8c6f 100644 --- a/OneBranchPipelines/stages/build-windows-single-stage.yml +++ b/OneBranchPipelines/stages/build-windows-single-stage.yml @@ -36,6 +36,12 @@ parameters: - name: installOdbcWheel type: boolean default: false + # Sign the native Python extension (.pyd) with ESRP (CP-230012) before it is + # packaged into the wheel. Runs in BOTH Official and NonOfficial runs. Set to + # false only for fast local/dev iterations where ESRP creds are unavailable. + - name: signWindowsBinaries + type: boolean + default: true stages: - stage: ${{ parameters.stageName }} @@ -248,6 +254,30 @@ stages: displayName: 'Build PYD for $(targetArch)' continueOnError: false + # ========================= + # ESRP CODE SIGNING (Windows native extension) + # ========================= + # Authenticode-sign the freshly built ddbc_bindings.*.pyd with CP-230012 + # BEFORE it is copied to the bindings/apiScan artifacts and packaged into + # the wheel by setup.py bdist_wheel. Signing in place (rather than + # unpack/repack of the .whl) keeps wheel RECORD hashes correct. + # + # Runs in BOTH Official and NonOfficial runs (no oneBranchType gate) so + # every published wheel contains a signed extension. mssql_py_core is + # installed LATER (below), so at this point only ddbc_bindings.*.pyd is + # present in mssql_python\ - the pattern further scopes signing to it. + - ${{ if eq(parameters.signWindowsBinaries, true) }}: + - template: /OneBranchPipelines/steps/compound-esrp-code-signing-step.yml@self + parameters: + appRegistrationClientId: '$(SigningAppRegistrationClientId)' + appRegistrationTenantId: '$(SigningAppRegistrationTenantId)' + authAkvName: '$(SigningAuthAkvName)' + authSignCertName: '$(SigningAuthSignCertName)' + esrpClientId: '$(SigningEsrpClientId)' + esrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + signPath: '$(Build.SourcesDirectory)\mssql_python' + pattern: 'ddbc_bindings.cp$(shortPyVer)-*.pyd' + # ========================= # MSSQL_PY_CORE INSTALLATION # ========================= @@ -354,6 +384,60 @@ stages: python setup.py bdist_wheel displayName: 'Build wheel package' + # ========================= + # SIGNED-WHEEL EVIDENCE (verification only) + # ========================= + # Prove that the .pyd embedded in the freshly built wheel is + # Authenticode-signed. This is a read-only check (unpack to a temp dir and + # inspect) - it does NOT modify or repack the wheel, so RECORD hashes are + # untouched. Provides the signing evidence attached to the GitHub PR. + - ${{ if eq(parameters.signWindowsBinaries, true) }}: + - pwsh: | + $ErrorActionPreference = 'Stop' + + python -m pip install --upgrade wheel | Out-Null + + $verifyRoot = Join-Path "$(Agent.TempDirectory)" "wheel-sign-verify" + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $verifyRoot + New-Item -ItemType Directory -Force -Path $verifyRoot | Out-Null + + $wheels = @(Get-ChildItem "$(Build.SourcesDirectory)\dist" -Filter *.whl -File) + if ($wheels.Count -eq 0) { + Write-Error "No wheel found in dist\ to verify" + exit 1 + } + + foreach ($wheel in $wheels) { + Write-Host "Unpacking $($wheel.Name) for signature verification" + $dest = Join-Path $verifyRoot $wheel.BaseName + New-Item -ItemType Directory -Force -Path $dest | Out-Null + python -m wheel unpack "$($wheel.FullName)" --dest "$dest" + } + + $nativeFiles = @(Get-ChildItem $verifyRoot -Recurse -Include ddbc_bindings.*.pyd -File) + if ($nativeFiles.Count -eq 0) { + Write-Error "No ddbc_bindings .pyd found inside the built wheel(s)" + exit 1 + } + + $invalid = @() + foreach ($file in $nativeFiles) { + $signature = Get-AuthenticodeSignature $file.FullName + Write-Host "$($file.Name): $($signature.Status) [$($signature.SignerCertificate.Subject)]" + if ($signature.Status -ne 'Valid') { + $invalid += $file.FullName + } + } + + if ($invalid.Count -gt 0) { + Write-Error "Wheel contains unsigned/invalid native binaries:`n$($invalid -join "`n")" + exit 1 + } + + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $verifyRoot + Write-Host "Verified: the .pyd inside the built wheel is Authenticode 'Valid'." + displayName: 'Verify signed .pyd inside built wheel' + # ========================= # ARTIFACT PUBLISHING # ========================= diff --git a/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml b/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml index 62c9357fd..344224cd9 100644 --- a/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml +++ b/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml @@ -1,210 +1,162 @@ -''' -ESRP Code Signing Step Template (DISABLED - Python wheels cannot be signed with SignTool) +# ========================================================================================= +# ESRP Code Signing Step Template - Native Windows binaries (.pyd / .dll) +# ========================================================================================= +# Signs native PE-format binaries (the compiled Python extension `ddbc_bindings.*.pyd` +# and any bundled `.dll`) using Microsoft's Enterprise Secure Release Process (ESRP) +# with the CP-230012 Authenticode certificate. +# +# WHY WE SIGN THE .pyd AND NOT THE .whl +# ------------------------------------- +# A Python wheel (`.whl`) is a ZIP archive, not a PE-format binary, so Windows +# SignTool / ESRP SigntoolSign cannot Authenticode-sign the wheel itself. The +# supported approach is to sign the native binary *inside* the wheel. In this repo +# the extension `.pyd` is built in a dedicated step BEFORE `setup.py bdist_wheel` +# packages it, so we sign the `.pyd` in place. The signed binary then flows into: +# - the `.whl` produced by bdist_wheel (RECORD hashes stay correct because we sign +# before packaging - no unpack/repack/regenerate needed), +# - the standalone `bindings/windows` artifact, and +# - the apiScan copy used by SDL binary scanning. +# +# This template runs in BOTH Official and NonOfficial pipeline runs so every wheel +# we publish (from any pipeline flavor) contains a signed extension. +# +# Certificate: CP-230012 "SHA256 Authenticode" (Microsoft Corporation, external distribution) +# Operation: SigntoolSign + SigntoolVerify (Windows SignTool - PE-format binaries) +# ========================================================================================= +parameters: + - name: appRegistrationClientId + type: string + displayName: 'ESRP App Registration Client ID' -This template was originally designed to handle signing of binary artifacts using Enterprise Secure Release Process (ESRP). -However, we discovered that Python wheel (.whl) files cannot be signed using Windows SignTool because: + - name: appRegistrationTenantId + type: string + displayName: 'ESRP App Registration Tenant ID' -1. Python wheels are ZIP archive files, not PE format binaries -2. Windows SignTool only supports PE format files (.exe, .dll, .sys, etc.) -3. ZIP archives require different signing approaches (if supported at all) + - name: authAkvName + type: string + displayName: 'Azure Key Vault name holding the signing certificate' -Error Messages Encountered: + - name: authSignCertName + type: string + displayName: 'Signing certificate name' -ESRP Error Log: -"SignTool Error: This file format cannot be signed because it is not recognized." + - name: esrpClientId + type: string + displayName: 'ESRP Client ID' -Full SignTool Command that Failed: -sign /NPH /fd "SHA256" /f "..." /tr "..." /d "mssql-python" "...whl" + - name: esrpConnectedServiceName + type: string + displayName: 'ESRP Connected Service Name (federated MSI)' -Technical Details: -- Certificate CP-230012 loads successfully and authentication works correctly -- File upload to ESRP service works without issues -- The failure occurs when SignTool attempts to process the .whl file -- SignTool recognizes .whl as an unknown/unsupported format + - name: signPath + type: string + displayName: 'Folder containing the native binaries to sign' -Alternative Approaches Considered: -1. OneBranch signing (onebranch.pipeline.signing@1) - had authentication issues requiring interactive login -2. Different ESRP operations - no ESRP operation exists for ZIP archive signing -3. Signing individual files within wheels - would break wheel integrity and PyPI compatibility + - name: pattern + type: string + default: '*.pyd' + displayName: 'Comma-separated pattern of files to sign (e.g. ddbc_bindings.cp312-*.pyd)' -Conclusion: -Python wheels distributed to PyPI are typically unsigned. The package integrity is verified through -checksums and PyPIs own security mechanisms. Many popular Python packages on PyPI are not code-signed. +steps: + # ----------------------------------------------------------------------------- + # 1) Malware scan the native binaries before signing (ESRP compliance gate). + # ----------------------------------------------------------------------------- + - task: EsrpMalwareScanning@5 + displayName: 'ESRP MalwareScanning - Native binaries' + inputs: + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + UseMSIAuthentication: true + FolderPath: '${{ parameters.signPath }}' + Pattern: '${{ parameters.pattern }}' + SessionTimeout: 60 + CleanupTempStorage: 1 + VerboseLogin: 1 -This template is preserved for reference and potential future use if alternative signing approaches -are identified or if other file types need to be signed. + # ----------------------------------------------------------------------------- + # 2) Authenticode-sign the native binaries with CP-230012. + # ----------------------------------------------------------------------------- + - task: EsrpCodeSigning@5 + displayName: 'ESRP CodeSigning - Native binaries (.pyd)' + inputs: + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + UseMSIAuthentication: true + AuthAKVName: '${{ parameters.authAkvName }}' + AuthSignCertName: '${{ parameters.authSignCertName }}' + FolderPath: '${{ parameters.signPath }}' + Pattern: '${{ parameters.pattern }}' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolSign", + "parameters": [ + { + "parameterName": "OpusName", + "parameterValue": "mssql-python" + }, + { + "parameterName": "OpusInfo", + "parameterValue": "https://www.microsoft.com" + }, + { + "parameterName": "FileDigest", + "parameterValue": "/fd \"SHA256\"" + }, + { + "parameterName": "PageHash", + "parameterValue": "/NPH" + }, + { + "parameterName": "TimeStamp", + "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolVerify", + "parameters": [], + "toolName": "sign", + "toolVersion": "1.0" + } + ] -Original Configuration Details: -CP-230012: "SHA256 Authenticode Standard Microsoft Corporation" certificate for external distribution -Operation: SigntoolSign (Windows SignTool for PE format binaries only) -Service Connection: Microsoft Release Management Internal + # ----------------------------------------------------------------------------- + # 3) Independent Authenticode verification gate - fail the build if any native + # binary is unsigned or has an invalid signature. Prints signer subject as + # evidence in the build log. + # ----------------------------------------------------------------------------- + - pwsh: | + $ErrorActionPreference = 'Stop' -Based on SqlClient ESRP signing implementation -COMMENTED OUT - All ESRP signing tasks are disabled due to SignTool incompatibility with wheel files -The code below is preserved for reference and potential future use with other file types -''' -# parameters: -# - name: appRegistrationClientId -# type: string -# displayName: 'App Registration Client ID' -# -# - name: appRegistrationTenantId -# type: string -# displayName: 'App Registration Tenant ID' -# -# - name: artifactType -# type: string -# displayName: 'Artifact type to sign' -# values: -# - 'dll' # For .pyd, .so, .dylib files (native binaries) -# - 'whl' # For .whl files (Python wheels) -# -# - name: authAkvName -# type: string -# displayName: 'Azure Key Vault name' -# -# - name: authSignCertName -# type: string -# displayName: 'Signing certificate name' -# -# - name: esrpClientId -# type: string -# displayName: 'ESRP Client ID' -# -# - name: esrpConnectedServiceName -# type: string -# displayName: 'ESRP Connected Service Name' -# -# - name: signPath -# type: string -# displayName: 'Path containing files to sign' + $files = @(Get-ChildItem -Path '${{ parameters.signPath }}' -Recurse -Include *.pyd,*.dll -File) + if ($files.Count -eq 0) { + Write-Error "No native binaries (*.pyd, *.dll) found to verify under '${{ parameters.signPath }}'" + exit 1 + } -# steps: -# # Sign native binary files (.pyd, .so, .dylib) -# - ${{ if eq(parameters.artifactType, 'dll') }}: -# - task: EsrpCodeSigning@5 -# displayName: 'ESRP CodeSigning - Native Binaries' -# inputs: -# ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' -# AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' -# AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' -# EsrpClientId: '${{ parameters.esrpClientId }}' -# UseMSIAuthentication: true -# AuthAKVName: '${{ parameters.authAkvName }}' -# AuthSignCertName: '${{ parameters.authSignCertName }}' -# FolderPath: '${{ parameters.signPath }}' -# Pattern: '*.pyd,*.dll,*.so,*.dylib' -# signConfigType: inlineSignParams -# inlineOperation: | -# [ -# { -# "keyCode": "CP-230012", -# "operationSetCode": "SigntoolSign", -# "parameters": [ -# { -# "parameterName": "OpusName", -# "parameterValue": "mssql-python" -# }, -# { -# "parameterName": "OpusInfo", -# "parameterValue": "http://www.microsoft.com" -# }, -# { -# "parameterName": "FileDigest", -# "parameterValue": "/fd \"SHA256\"" -# }, -# { -# "parameterName": "PageHash", -# "parameterValue": "/NPH" -# }, -# { -# "parameterName": "TimeStamp", -# "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" -# } -# ], -# "toolName": "sign", -# "toolVersion": "1.0" -# }, -# { -# "keyCode": "CP-230012", -# "operationSetCode": "SigntoolVerify", -# "parameters": [], -# "toolName": "sign", -# "toolVersion": "1.0" -# } -# ] -# -# # Sign Python wheel files (.whl) -# - ${{ if eq(parameters.artifactType, 'whl') }}: -# - task: EsrpCodeSigning@5 -# displayName: 'ESRP CodeSigning - Python Wheels' -# inputs: -# ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' -# AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' -# AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' -# EsrpClientId: '${{ parameters.esrpClientId }}' -# UseMSIAuthentication: true -# AuthAKVName: '${{ parameters.authAkvName }}' -# AuthSignCertName: '${{ parameters.authSignCertName }}' -# FolderPath: '${{ parameters.signPath }}' -# Pattern: '*.whl' -# signConfigType: inlineSignParams -# inlineOperation: | -# [ -# { -# "keyCode": "CP-230012", -# "operationSetCode": "SigntoolSign", -# "parameters": [ -# { -# "parameterName": "OpusName", -# "parameterValue": "mssql-python" -# }, -# { -# "parameterName": "OpusInfo", -# "parameterValue": "http://www.microsoft.com" -# }, -# { -# "parameterName": "FileDigest", -# "parameterValue": "/fd \"SHA256\"" -# }, -# { -# "parameterName": "PageHash", -# "parameterValue": "/NPH" -# }, -# { -# "parameterName": "TimeStamp", -# "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" -# } -# ], -# "toolName": "sign", -# "toolVersion": "1.0" -# }, -# { -# "keyCode": "CP-230012", -# "operationSetCode": "SigntoolVerify", -# "parameters": [], -# "toolName": "sign", -# "toolVersion": "1.0" -# } -# ] -# -# # List signed files (platform-specific) -# - ${{ if eq(parameters.artifactType, 'dll') }}: -# # Windows - use cmd syntax -# - script: | -# echo Signed files in: ${{ parameters.signPath }} -# dir /s /b "${{ parameters.signPath }}\*.whl" "${{ parameters.signPath }}\*.pyd" "${{ parameters.signPath }}\*.dll" 2>nul -# displayName: 'List signed files (Windows)' -# condition: succeededOrFailed() -# -# - ${{ else }}: -# # Linux/macOS - use bash syntax -# - bash: | -# echo "Signed files in: ${{ parameters.signPath }}" -# if [ -d "${{ parameters.signPath }}" ]; then -# find "${{ parameters.signPath }}" -type f \( -name "*.whl" -o -name "*.pyd" -o -name "*.dll" -o -name "*.so" -o -name "*.dylib" \) -ls -# else -# echo "Directory not found: ${{ parameters.signPath }}" -# fi -# displayName: 'List signed files (Linux/macOS)' -# condition: succeededOrFailed() + $invalid = @() + foreach ($file in $files) { + $signature = Get-AuthenticodeSignature $file.FullName + Write-Host "$($file.Name): $($signature.Status) [$($signature.SignerCertificate.Subject)]" + if ($signature.Status -ne 'Valid') { + $invalid += $file.FullName + } + } + + if ($invalid.Count -gt 0) { + Write-Error "Unsigned or invalid native binaries:`n$($invalid -join "`n")" + exit 1 + } + + Write-Host "All native binaries are Authenticode 'Valid'." + displayName: 'Verify Authenticode signatures (native binaries)' From 632fa72c08dda73576d7972b3b0fbb626865dcda Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:15:26 -0700 Subject: [PATCH 2/2] FIX: Scope ESRP signing to the exact .pyd name and align verify scope Address code-review feedback on the Windows native-extension signing: - Sign a specifically-named file instead of a broad net. The caller now passes the exact extension name (ddbc_bindings.cp-.pyd via a new pydArch stage variable: x64->amd64, arm64->arm64, x86->win32) rather than a ddbc_bindings.cp-*.pyd wildcard. - Make the template pattern parameter required (drop the *.pyd default) so callers must scope signing explicitly. - Verification gate now derives its file list from the same pattern that was signed instead of rescanning all *.pyd/*.dll, so malware-scan, sign, and verify scopes stay identical (previously it could fail on unrelated binaries the caller intentionally excluded). - Fix a misleading comment that claimed only ddbc_bindings.*.pyd is present in mssql_python\ during signing; build.bat also copies the vcredist msvcp140.dll there, which is already Microsoft-signed and not ours to sign. AB#46467 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 72f9edcc-d3cb-4bec-beed-f90445857f80 --- .../stages/build-windows-single-stage.yml | 19 +++++-- .../steps/compound-esrp-code-signing-step.yml | 49 ++++++++++++------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/OneBranchPipelines/stages/build-windows-single-stage.yml b/OneBranchPipelines/stages/build-windows-single-stage.yml index 68b0d8c6f..db1c4b1eb 100644 --- a/OneBranchPipelines/stages/build-windows-single-stage.yml +++ b/OneBranchPipelines/stages/build-windows-single-stage.yml @@ -71,6 +71,15 @@ stages: shortPyVer: ${{ parameters.shortPyVer }} # Target architecture (can differ from host for cross-compilation) targetArch: ${{ parameters.architecture }} + # Arch suffix used in the built extension's file name. build.bat maps the + # target arch to the wheel/PE tag: x64 -> amd64, arm64 -> arm64, x86 -> win32. + # Used to name the exact .pyd for signing (no wildcards). + ${{ if eq(parameters.architecture, 'arm64') }}: + pydArch: 'arm64' + ${{ elseif eq(parameters.architecture, 'x86') }}: + pydArch: 'win32' + ${{ else }}: + pydArch: 'amd64' # System access token for authenticated downloads (e.g., GitHub artifacts) SYSTEM_ACCESSTOKEN: $(System.AccessToken) @@ -263,9 +272,11 @@ stages: # unpack/repack of the .whl) keeps wheel RECORD hashes correct. # # Runs in BOTH Official and NonOfficial runs (no oneBranchType gate) so - # every published wheel contains a signed extension. mssql_py_core is - # installed LATER (below), so at this point only ddbc_bindings.*.pyd is - # present in mssql_python\ - the pattern further scopes signing to it. + # every published wheel contains a signed extension. The `pattern` names + # the exact .pyd for THIS Python version + arch (e.g. + # ddbc_bindings.cp312-amd64.pyd) so signing is scoped precisely to our own + # extension - NOT the vcredist msvcp140.dll that build.bat also copies next + # to it (that DLL is already signed by Microsoft and is not ours to sign). - ${{ if eq(parameters.signWindowsBinaries, true) }}: - template: /OneBranchPipelines/steps/compound-esrp-code-signing-step.yml@self parameters: @@ -276,7 +287,7 @@ stages: esrpClientId: '$(SigningEsrpClientId)' esrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' signPath: '$(Build.SourcesDirectory)\mssql_python' - pattern: 'ddbc_bindings.cp$(shortPyVer)-*.pyd' + pattern: 'ddbc_bindings.cp$(shortPyVer)-$(pydArch).pyd' # ========================= # MSSQL_PY_CORE INSTALLATION diff --git a/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml b/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml index 344224cd9..5451d5cf8 100644 --- a/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml +++ b/OneBranchPipelines/steps/compound-esrp-code-signing-step.yml @@ -1,9 +1,16 @@ # ========================================================================================= -# ESRP Code Signing Step Template - Native Windows binaries (.pyd / .dll) +# ESRP Code Signing Step Template - Windows native Python extension (.pyd) # ========================================================================================= -# Signs native PE-format binaries (the compiled Python extension `ddbc_bindings.*.pyd` -# and any bundled `.dll`) using Microsoft's Enterprise Secure Release Process (ESRP) -# with the CP-230012 Authenticode certificate. +# Authenticode-signs OUR own PE-format binary - the compiled Python extension +# `ddbc_bindings.*.pyd` - using Microsoft's Enterprise Secure Release Process (ESRP) +# with the CP-230012 certificate. +# +# SCOPE: The caller passes an explicit `pattern` naming exactly the file(s) we own +# (e.g. `ddbc_bindings.cp312-amd64.pyd`). We deliberately do NOT sign with a broad +# `*.pyd` / `*.dll` net: other binaries that may sit next to our extension (e.g. the +# vcredist `msvcp140.dll` copied by build.bat) are already signed by their publishers +# and are not ours to re-sign. Malware scanning, signing, and the verification gate +# below all operate on the same explicit `pattern` so the three scopes stay identical. # # WHY WE SIGN THE .pyd AND NOT THE .whl # ------------------------------------- @@ -50,19 +57,18 @@ parameters: - name: signPath type: string - displayName: 'Folder containing the native binaries to sign' + displayName: 'Folder containing the native extension to sign' - name: pattern type: string - default: '*.pyd' - displayName: 'Comma-separated pattern of files to sign (e.g. ddbc_bindings.cp312-*.pyd)' + displayName: 'Explicit file name(s) to sign, comma-separated (e.g. ddbc_bindings.cp312-amd64.pyd). No default - callers MUST scope this to the specific files they own.' steps: # ----------------------------------------------------------------------------- - # 1) Malware scan the native binaries before signing (ESRP compliance gate). + # 1) Malware scan the native extension before signing (ESRP compliance gate). # ----------------------------------------------------------------------------- - task: EsrpMalwareScanning@5 - displayName: 'ESRP MalwareScanning - Native binaries' + displayName: 'ESRP MalwareScanning - Native extension' inputs: ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' @@ -76,10 +82,10 @@ steps: VerboseLogin: 1 # ----------------------------------------------------------------------------- - # 2) Authenticode-sign the native binaries with CP-230012. + # 2) Authenticode-sign the native extension with CP-230012. # ----------------------------------------------------------------------------- - task: EsrpCodeSigning@5 - displayName: 'ESRP CodeSigning - Native binaries (.pyd)' + displayName: 'ESRP CodeSigning - Native extension (.pyd)' inputs: ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' @@ -131,16 +137,21 @@ steps: ] # ----------------------------------------------------------------------------- - # 3) Independent Authenticode verification gate - fail the build if any native - # binary is unsigned or has an invalid signature. Prints signer subject as - # evidence in the build log. + # 3) Independent Authenticode verification gate - fail the build if the file(s) + # we just signed are unsigned or have an invalid signature. Uses the SAME + # explicit `pattern` as the sign step so the verification scope matches the + # signing scope exactly (no broad *.pyd/*.dll rescan). Prints the signer + # subject as evidence in the build log. # ----------------------------------------------------------------------------- - pwsh: | $ErrorActionPreference = 'Stop' - $files = @(Get-ChildItem -Path '${{ parameters.signPath }}' -Recurse -Include *.pyd,*.dll -File) + # Same explicit, comma-separated pattern that was signed above. + $patterns = @('${{ parameters.pattern }}'.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + + $files = @(Get-ChildItem -Path '${{ parameters.signPath }}' -Recurse -Include $patterns -File) if ($files.Count -eq 0) { - Write-Error "No native binaries (*.pyd, *.dll) found to verify under '${{ parameters.signPath }}'" + Write-Error "No files matching pattern(s) '$($patterns -join ", ")' found to verify under '${{ parameters.signPath }}'" exit 1 } @@ -154,9 +165,9 @@ steps: } if ($invalid.Count -gt 0) { - Write-Error "Unsigned or invalid native binaries:`n$($invalid -join "`n")" + Write-Error "Unsigned or invalid signed file(s):`n$($invalid -join "`n")" exit 1 } - Write-Host "All native binaries are Authenticode 'Valid'." - displayName: 'Verify Authenticode signatures (native binaries)' + Write-Host "All signed file(s) are Authenticode 'Valid'." + displayName: 'Verify Authenticode signature (native extension)'